Skip to content

feat(mobile): give Profile rows a curated shared icon palette - #6613

Merged
iscekic merged 1 commit into
mainfrom
kwf/profile-row-icon-palette-2f83
Sep 23, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/profile-row-icon-palette-2f83

Conversation

@iscekic

@iscekic iscekic commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Changelog for users

  • Profile screen row icons now use a curated colour palette instead of rendering plain white in dark theme.
  • A destination keeps the same colour in light and dark theme and in every language.
  • Code Reviewer and Security Agent share honey; PR Review is gold; Manage organization is lime; Preferences and Tutorial are sage; linked accounts are moss.
  • Feedback, Privacy choices and Sign out share fern; Delete account keeps its destructive red treatment and its confirmation.
  • The bottom tab bar keeps its existing foreground / muted-foreground tints.

Changelog for maintainers

  • IconTile stays the one tile treatment; ConfigureRow and ActionTile now take an explicit hue prop naming a palette step, and nothing derives a colour from a label, title, translation or hash.
  • The palette lives in the colour module as RowHue, ROW_PALETTE and rowTint(), with a comment stating the choosing rule, the destination table and the growth rule for new rows.
  • The theme stylesheet defines --row-honey through --row-fern as a hue plus a 10%-alpha tile background and a 20%-alpha border in both themes; the generated colour map is regenerated and the assertion script passes.
  • ConfigureRow precedence is tone > hue > neutral, so settings rows outside Profile still render the neutral tile.
  • ActionTile requires a hue; destructive still overrides it with toneColor('danger'), and agentColor and the agent surfaces are unchanged.
  • The bottom tab bar tints are unchanged; a comment records that the bar signals a selected tab, not a destination, so one accent per tab would fight the rows below.
  • Tests pin the hue per destination and prove the same row renders the same tint in English and Serbian; the contrast test asserts every curated entry clears 3:1 in both themes.
  • Measured non-text contrast of the glyph against its tinted tile, light / dark: honey 3.98 / 9.07, gold 3.41 / 9.95, lime 3.56 / 9.65, sage 3.71 / 9.40, moss 3.83 / 9.16, fern 3.92 / 8.96. The lowest is gold in light at 3.41:1.
Row (destination) Hue Light hex Dark hex Contrast vs tinted tile (light / dark) Reason for that hue
Code Reviewer honey #7C6C1D #EBDA84 3.98 / 9.07 Agents section, the first family step next to the brand yellow.
Security Agent honey #7C6C1D #EBDA84 3.98 / 9.07 Same Agents section, so it shares the section hue.
PR Review gold #7A7C1D #E9EB84 3.41 / 9.95 Reviews section, the next step in reading order.
Manage / View organization lime #697C1D #D6EB84 3.56 / 9.65 Organization section, next step.
Preferences sage #587C1D #C4EB84 3.71 / 9.40 App section, next step.
Tutorial sage #587C1D #C4EB84 3.71 / 9.40 Same App section, so it shares the section hue.
Linked account (provider) row moss #467C1D #B1EB84 3.83 / 9.16 Linked-accounts section, next step.
Feedback fern #357C1D #9EEB84 3.92 / 8.96 Actions section at the bottom of the screen.
Privacy choices fern #357C1D #9EEB84 3.92 / 8.96 Same Actions section.
Sign out fern #357C1D #9EEB84 3.92 / 8.96 Same Actions section.
Delete account danger tone, not a family step #B0483A #F28B7A destructive treatment kept Destructive stays reserved; a curated row hue is never a status colour.

The six family steps walk one warm band at a fixed chroma/value per theme, straddling the brand olive and yellow; the reserved status hues (danger, warn, good, info) sit outside that band, so no row reads as a status colour. The same-row-same-tint checks are the assertions that the colour no longer depends on the label.

E2E proof

[e1] Dark theme: switch Appearance to Dark in Preferences, return to Profile — android: repaired the stale parked steps (old tap 'Dark' MISSed) and ran state.sh settings then the scene; 'SCENE e1 OK' with the post-switch Profile digest (Preferences, Tutorial, Feedback, Privacy choices, Sign out, Delete Account) proves the state move, while the dark rendering itself is the visual reviewer's call (e1.png).

Happy (English, current theme): signed-in Profile renders the six curated rows and the four action tiles; the verifier stills this screen as the EN still of the light/dark pair. — e1.png

Light theme: switch Appearance to Light in Preferences, return to Profile and still the light screen (the second half of the both-themes proof). — e4.png

Neutral surfaces unchanged: the Preferences hub rows (configure-row without a hue) still render, so the palette did not reach the settings lists outside the Profile screen. — e6.png

ux-check: Dark theme, Serbian locale, Profile: none of the six row glyphs (Code Reviewer, Security Agent, PR Review, Manage Organization, Preferences, Tutorial) renders near-white; each shows a… — e7-bottom.png

ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device). — e11-profile-dark.png

ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages. — e9.png

[e7] ux-check: Dark theme, Serbian locale, Profile: none of the six row glyphs (Code Reviewer, Security Agent, PR Review, Manage Organization, Preferences, Tutorial) renders near-white; each shows a… — Dark theme is proven live by e7-prefs-dark.txt (RadioButton content-desc="Tamno" checked="true") and the Serbian Profile renders all six rows in one digest, e7-org.txt (Agent za pregled koda / Bezbednosni agent / Pregled PR-a / Pogledaj organizaciju / Podešavanja / Tutorijal), with e7-bottom.txt showing Povratne informacije, Podešavanja privatnosti, Odjavi se, Obriši nalog; the glyph hue on the tiles is the visual reviewer's judgement on the same run's e7-org.png/e7-bottom.png, and in the Personal context the org row is absent (e7-personal.txt) so it was observed in the org context instead.

[e7] ux-check: Dark theme, Serbian locale, Profile: none of the six row glyphs (Code Reviewer, Security Agent, PR Review, Manage Organization, Preferences, Tutorial) renders near-white; each shows a… — prior/e7-prefs-dark.png

[e4] Light theme: switch Appearance to Light in Preferences, return to Profile and still the light screen (the second half of the both-themes proof). — android/emulator-5554: repaired parked list (Preferences anchor matched the Profile row and never navigated); run ends on the Profile screen (e4-dispatch2.log: 'SCENE e4 OK', 'android.view.View Profile', 'Code Reviewer') and the Light selection persisted after the round trip (e4-light-checked.txt: 'content-desc="Light" checkable="true" checked="true"'); light Profile/Preferences stills e4.png, e4-light-checked.png for the visual reviewer.

[e4] Light theme: switch Appearance to Light in Preferences, return to Profile and still the light screen (the second half of the both-themes proof). — prior/e4-light-checked.png

[e7] ux-check: Dark theme, Serbian locale, Profile: none of the six row glyphs (Code Reviewer, Security Agent, PR Review, Manage Organization, Preferences, Tutorial) renders near-white; each shows a…

[e7] ux-check: Dark theme, Serbian locale, Profile: none of the six row glyphs (Code Reviewer, Security Agent, PR Review, Manage Organization, Preferences, Tutorial) renders near-white; each shows a… — prior/e7-org.png

[e5] Serbian locale Profile rows in the same order, then switch back to English — android; corrected replay landed OK — e5-scene.log line 215 'SCENE e5 OK' — and its digest shows the SR Profile rows in the English order: 'android.widget.TextView Agent za pregled koda' (147), 'Bezbednosni agent' (150), 'Pregled PR-a' (154), 'Tutorijal' (161), with the SR bar 'android.widget.Button Početna, kartica, 1 od 3' (163); the four SR action tiles 'Povratne informacije'/'Podešavanja privatnosti'/'Odjavi se'/'Obriši nalog' are in e5-bottom-scene.log ('SCENE e5-bottom OK' line 38, tiles 57-64); stills e5-sr-profile.png, e5-light-sr-profile.png, e5-en-profile.png, e5-bottom.png; replay…

[e5] Serbian locale Profile rows in the same order, then switch back to English — prior/e5-sr-profile.png

[e5] Serbian locale Profile rows in the same order, then switch back to English

[e5] Serbian locale Profile rows in the same order, then switch back to English — prior/e5-light-sr-profile.png

[e1] Dark theme: switch Appearance to Dark in Preferences, return to Profile

[e1] Dark theme: switch Appearance to Dark in Preferences, return to Profile — e2e-mobile-app/e1.png

E2E proof — log excerpts

[e1] Dark theme: switch Appearance to Dark in Preferences, return to Profile -> pass :: android: repaired the stale parked steps (old tap 'Dark' MISSed) and ran state.sh settings then the scene; 'SCENE e1 OK' with the post-switch Profile digest (Preferences, Tutorial, Feedback, Privacy choices, Sign out, Delete Account) proves the state move, while the dark rendering itself is the visual reviewer's call (e1.png).
[e2] ux-check: Bottom Actions rows share the fern hue, Delete account stays dest -> pass :: android: state.sh signed-in-home then the harvested replay; 'SCENE e2 OK' with digest rows 'android.widget.Button Feedback', 'android.widget.Button Privacy choices', 'android.widget.Button Sign out' and 'android.widget.Button Delete Account' after the destructive confirm/Cancel branch; the fern-vs-red glyph distinction is a visual claim owned by the visual reviewer (e2.png).
/home/igor_kilocode_ai/.local/share/kwf/sections/profile-row-icon-palette-2f83/e2e-mobile-app/e1-run4.log
android.widget.TextView Preferences tappable [171,697][953,743]
android.widget.TextView Appearance, notifications, thinking, and screen behavior tappable [171,747][953,784]
android.widget.Button Tutorial tappable [37,840][1043,974]
android.widget.TextView Tutorial tappable [171,884][953,930]
android.widget.TextView LINKED ACCOUNTS tappable [37,1029][1045,1073]
android.widget.TextView Email tappable [171,1129][1017,1175]
android.widget.TextView e2e-mobile-profile-row-icon-palette-2f83-android@example.com tappable [171,1179][1017,1216]
android.widget.TextView Test Account tappable [171,1300][1017,1346]
android.widget.TextView e2e-mobile-profile-row-icon-palette-2f83-android@example.com tappable [171,1350][1017,1387]
android.widget.Button Feedback tappable [37,1470][1043,1604]
android.widget.TextView Feedback tappable [170,1513][1016,1559]
android.widget.Button Privacy choices tappable [37,1631][1043,1765]
android.widget.TextView Privacy choices tappable [170,1674][1016,1720]
android.widget.Button Sign out tappable [37,1793][1043,1927]
android.widget.TextView Sign out tappable [170,1836][1016,1882]
android.widget.Button Delete Account tappable [37,1954][1043,2088]
android.widget.TextView Delete Account tappable [170,1997][1016,2043]
android.widget.TextView v1.0.12 (1) tappable [37,2115][1045,2152]
android.widget.Button Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.widget.Button Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.widget.Button Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
/home/igor_kilocode_ai/.local/share/kwf/sections/profile-row-icon-palette-2f83/e2e-mobile-app/e2-run.log
android.widget.TextView Preferences tappable [171,697][953,743]
android.widget.TextView Appearance, notifications, thinking, and screen behavior tappable [171,747][953,784]
android.widget.Button Tutorial tappable [37,840][1043,974]
android.widget.TextView Tutorial tappable [171,884][953,930]
android.widget.TextView LINKED ACCOUNTS tappable [37,1029][1045,1073]
android.widget.TextView Email tappable [171,1129][1017,1175]
android.widget.TextView e2e-mobile-profile-row-icon-palette-2f83-android@example.com tappable [171,1179][1017,1216]
android.widget.TextView Test Account tappable [171,1300][1017,1346]
android.widget.TextView e2e-mobile-profile-row-icon-palette-2f83-android@example.com tappable [171,1350][1017,1387]
android.widget.Button Feedback tappable [37,1470][1043,1604]
android.widget.TextView Feedback tappable [170,1513][1016,1559]
android.widget.Button Privacy choices tappable [37,1631][1043,1765]
android.widget.TextView Privacy choices tappable [170,1674][1016,1720]
android.widget.Button Sign out tappable [37,1793][1043,1927]
android.widget.TextView Sign out tappable [170,1836][1016,1882]
android.widget.Button Delete Account tappable [37,1954][1043,2088]
android.widget.TextView Delete Account tappable [170,1997][1016,2043]
android.widget.TextView v1.0.12 (1) tappable [37,2115][1045,2152]
android.widget.Button Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.widget.Button Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.widget.Button Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
Owner request

Surface: mobile-app

The Profile screen's row icons have lost their colour and render plain white. Give them a curated,
deliberate palette, chosen once, in one shared place.

The symptom, as reported

On the Profile screen (Profil), dark theme, Serbian locale, the row icons render plain white.
Affected rows: Agent za pregled koda, Bezbednosni agent, Pregled PR-a,
Upravljaj organizacijom, Podešavanja, Tutorial. Reproduce it before you change anything.

The cause, confirmed in the source at 2f0d76d5d8f0

All six rows are ConfigureRow (apps/mobile/src/components/ui/configure-row.tsx), used from
apps/mobile/src/components/profile-screen.tsx:174-273. ConfigureRow renders:

const tint: Tint = tone ? toneColor(tone) : NEUTRAL_TINT;
...
const iconTile = <IconTile icon={Icon} tint={tint} />;

and NEUTRAL_TINT (configure-row.tsx:36-42) is:

{ tileBgClass: 'bg-hair-soft', tileBorderClass: 'border-border',
  hueThemeKey: 'secondaryForeground' }

IconTile (apps/mobile/src/components/ui/icon-tile.tsx) strokes the glyph with
colors[tint.hueThemeKey], so every one of these rows paints secondaryForeground — near-white in
dark theme — on a hair-soft tile that is 5% alpha. A white glyph on an invisible tile is exactly
"lost their colour, plain white".

The file's own comment records why it is that way:

A settings list is not a list of agents: hashing each title into the agent hue ramp gave a single
list (Language / Trusted hosts / Device sessions) three different accent tints.

That judgement was right about the mechanism and wrong about the conclusion. The hash was the
problem; removing colour altogether was not the fix. So do not restore the hash.

The second, separate fault: ActionTile (apps/mobile/src/components/profile-action-tile.tsx) still
derives its tint from the localized label:

<IconTile icon={Icon} tint={destructive ? toneColor('danger') : agentColor(label)} />

agentColor hashes the string modulo a six-entry ramp (apps/mobile/src/lib/agent-color.ts:112-122).
A colour that changes with the language is not a design decision, it is an accident.

What to build

1. One shared component owns the colour, and it takes the colour as a prop.

IconTile is already the shared treatment and every surface already routes through it. Make the
colour a first-class, explicit prop on that shared component, and have each row pass a hue it names.
Nothing may derive a colour from a label, a title, a translation, or a hash of any string. The same
row must render the same colour in English and in Serbian.

2. A curated, deliberate palette, anchored to the logo.

The Kilo mark is the yellow tile, and the brand tokens are already in the theme: --accent-soft
#e8f27a in both themes, --primary #4f5a10 olive in light and #e8f27a in dark
(apps/mobile/src/global.css:25,42,100,115). The palette must sit next to that yellow and read as
one family, not as six unrelated accents.

Not random, not per-row ad hoc. Concretely:

  • One named palette in one module, with a comment stating the rule that chose it.
  • One documented mapping: which destination takes which hue, and why. A reviewer must be able to
    read the table and agree with each entry.
  • A stated rule for the next row someone adds, so the palette does not drift.
  • Every entry defined for light and dark, keeping the existing token shape: a hue, a 10%-alpha
    tile background, a 20%-alpha tile border (see the --agent-*-tile-bg / -tile-border pattern in
    global.css:64-81 and the dark values at global.css:133-146).
  • The semantic tones keep their meaning. good / warn / danger stay reserved
    (toneColor in agent-color.ts), and a destructive row stays destructive. A curated row hue must
    never be confusable with a status colour.

3. Contrast is a requirement, not a nicety.

A 16px stroked glyph on a 30x30 tile is a non-text graphic, so WCAG asks for 3:1 against its tile and
against the row background. There is already a place to assert this:
apps/mobile/src/lib/hooks/use-theme-colors.contrast.test.ts. State the measured contrast for every
palette entry, in both themes, in the PR body.

4. The four items at the bottom of the Profile screen.

apps/mobile/src/components/profile-screen.tsx:345-365 renders four ActionTile rows: Feedback
(MessageSquare), Privacy choices (Lock), Sign out (LogOut), Delete account (Trash2). They are
the second surface where a colour is chosen today by hashing a translated string. Bring them into the
same palette, and keep Delete account on the destructive tone.

5. The bottom bar.

apps/mobile/src/app/(app)/(tabs)/_layout.tsx:166-167 tints the tabs with colors.foreground and
colors.mutedForeground, and the bar shows Početna / Agenti / Profil. Adjusting those two is in
scope if the curated palette calls for it. Say what you changed and why; a bar that now fights the
rows is worse than a plain one.

Non-goals

  • Do not change the agent hue ramp's use for agents. agentColor(name) is correct for a session
    row or an agent badge, where the colour identifies a stable agent name. This item is about menu
    rows, where the colour identifies a destination. Leave src/components/ui/session-row.tsx,
    src/components/ui/agent-badge.tsx, and the kiloclaw surfaces as they are, unless you find one of
    them rendering a menu row.
  • Do not hand-edit a generated file. apps/mobile/src/lib/hooks/theme-colors.generated.ts is
    generated from global.css by scripts/generate-theme-colors.mjs, and
    scripts/assert-theme-colors.mjs fails the suite when the two drift. Add tokens to global.css,
    then regenerate.
  • Do not remove toneColor, the Tint shape, or IconTile. Extend them.
  • No new dependency.

Files

  • apps/mobile/src/components/ui/icon-tile.tsx — the shared component that owns the colour prop.
  • apps/mobile/src/components/ui/configure-row.tsx — the six rows, and NEUTRAL_TINT.
  • apps/mobile/src/components/profile-action-tile.tsx — the four bottom rows.
  • apps/mobile/src/lib/agent-color.ts — where the palette and the curated mapping should live, or a
    new sibling module if that keeps the two jobs separate.
  • apps/mobile/src/global.css — the new hue tokens, light and dark.
  • apps/mobile/src/components/profile-screen.tsx — passing the explicit hue per row.
  • apps/mobile/src/app/(app)/(tabs)/_layout.tsx — the bottom bar.
  • apps/mobile/src/lib/hooks/use-theme-colors.contrast.test.ts — the contrast assertions.
  • apps/mobile/src/components/profile-action-tile.mounted.test.tsx and
    apps/mobile/src/components/profile-screen.mounted.test.tsx — the existing tests that may assert
    today's colours.

Do not edit a file outside this list.

Proof

Reproduce first. Capture the Profile screen in dark theme with the Serbian locale and show the
white icons. Then capture the same screen after the change. The change is visible, so the screenshots
are required, not optional.

  • Both themes, both languages. Four screenshots of the same screen: dark and light, English and
    Serbian. The colours must be identical between the two languages, row for row. Quote the
    decisive log lines or the test output that proves the tint no longer depends on the label.
  • A test that fails on today's code. Add one: rendering the same row in two locales must produce
    the same tint, and a curated row must not resolve to secondaryForeground. Add a second: every
    palette entry clears 3:1 in both themes.
  • The palette table in the PR body: row, hue, hex in light, hex in dark, measured contrast
    against the tile, and the reason for that hue on that row.
  • The bottom bar, captured before and after, if you change it.
  • Keep pnpm --filter @kilocode/mobile test green, plus the mounted suite the section runbook names.
    node scripts/assert-theme-colors.mjs must pass, which means the generated colours are in sync
    with global.css.

[p1] Before/after stills of the same screen: Profile screen — android; the worktree Profile screen renders — 'SCENE p1 OK' (p1-top.log line 1) with 'android.widget.Button Code Reviewer, Automatic PR reviews' (18), Security Agent, PR Review, Preferences, Tutorial in the digest — and the same replay's scroll run proves the four bottom action tiles: 'android.widget.Button Feedback' (p1-run2.log 20), 'android.widget.Button Privacy choices' (22), 'android.widget.Button Sign out' (24), 'android.widget.Button Delete Account' (26); still p1.png (top, shows the row icons) captured for the visual reviewer, who owns the before/after colour comparison; the…

[p1] Before/after stills of the same screen: Profile screen — p1.png

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages. — Light appearance was selected and is proven by the harness dump content-desc="Svetlo"" checkable="true" checked="true" in e9-prefs-sr.xml, and the same destinations render in the same list positions in both locales — android.widget.Button Code Reviewer, Automatic PR reviews tappable [37,1081][1043,1225] and android.widget.Button Feedback tappable [37,1470][1043,1604] in e9-en-scenes.log against android.widget.Button Agent za pregled koda, Automatski pregledi PR-ova tappable [37,1081][1043,1225] and android.widget.Button Povratne informacije tappable [37,1470][1043,1604] in…

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages. — e9-srb.png

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device). — android/emulator-5554: both themes reached (e11-dark-checked.txt: 'content-desc="Dark" checkable="true" checked="true"'; light in e4-light-checked.txt); computed WCAG non-text ratios for all six steps vs card and tinted tile are >=3:1 in both themes (e11-contrast.log: 'gold hue=#7A7C1D vsCard=3.82 vsTile=3.41', 'fern hue=#9EEB84 vsCard=11.46 vsTile=8.96'); Profile captures both themes (e11-profile-light.png, e11-profile-light-bottom.png, e11-profile-dark.png, e11-profile-dark-bottom.png) are for the visual reviewer — perceptual distinguishability is not my claim.

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device). — e11-profile-dark-bottom.png

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages.

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages. — e9sr.png

[e8] ux-check: English vs Serbian of the same screen and theme show identical row colours/tile class — android; e8-en.log and e8-sr.log both read 'SCENE … OK' and show the same rows in the same order at identical bounds (Code Reviewer/Agent za pregled koda [37,1016], Security Agent/Bezbednosni agent [37,1187], PR Review/Pregled PR-a [37,1457], Preferences/Podešavanja [37,1726], Tutorial/Tutorijal [37,1897], LINKED ACCOUNTS/POVEZANI NALOZI [37,2086]) with only the text localized, so translation does not change which row renders; glyph colour and tile class are visual, so the visual reviewer judges e8-en.png/e8-sr.png, and the tint's label-independence is the rowTint table plus the mounted EN/SR…

[e8] ux-check: English vs Serbian of the same screen and theme show identical row colours/tile class — e8-en.png

[e12] ux-check Bottom bar (Početna / Agenti / Profil) in both themes — android; 'SCENE e12-tabs OK' (e12-tabs-scene.log line 1) proves the bar is functional — tabs 'Home, tab, 1 of 3' (35), 'Agents, tab, 2 of 3' (37), 'Profile, tab, 3 of 3' (39) switch to 'Nothing running right now'/'Agents'/'Code Reviewer'; both-theme stills e12-dark-profile.png and e12-light-profile.png (Dark/Light radio checked) captured for the visual reviewer; the diff touches _layout.tsx comment-only, so the bar is unchanged; no behavioural UX-DEFECT observed.

[e12] ux-check Bottom bar (Početna / Agenti / Profil) in both themes — e12-dark-profile.png

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device).

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device). — e11-profile-light-bottom.png

[e12] ux-check Bottom bar (Početna / Agenti / Profil) in both themes

[e12] ux-check Bottom bar (Početna / Agenti / Profil) in both themes — e12-light-profile.png

[e8] ux-check: English vs Serbian of the same screen and theme show identical row colours/tile class

[e8] ux-check: English vs Serbian of the same screen and theme show identical row colours/tile class — e8-sr.png

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device).

[e11] ux-check: Every row glyph is perceptibly distinguishable from its own 30x30 tile and from the row card behind it in both themes (non-text 3:1 holds on device). — e11-profile-light.png

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages.

[e9] ux-check: Light theme, English and Serbian: every row glyph is visibly coloured and readable against its tile (not washed out), and the colours match row-for-row between the two languages. — e9b.png

Follow-ups (not changed here)

  • not proved live: Dark theme (the reported symptom): switch Appearance to Dark in Preferences, return to Profile and still the dark screen. The light counterpart uses the same path with 'Light' and is captured by the verifier. (no capture cited it)
  • not proved live: Serbian locale (the reported locale): switch the app to Serbian through Preferences > Account > Language, return to Profile and check the same rows in the same order with Serbian labels - the decisive proof that the label no longer picks the tint is in s2's unit test and in the SR stills the verifier captures here. The tail switches the app back to English so the locale does not leak into later scenarios; 'Podesavanja' also appears inside the Privacy choices label, so if the tail's Preferences tap lands on that tile, retry it against the Preferences row. (no capture cited it)
  • not proved live: [advisory] Before/after stills of the same screen: capture the Profile screen on the pre-change build (base commit 2f0d76d) in dark theme with the Serbian locale and show the plain-white row icons, then capture the same screen from the section worktree. Exact procedure: build and install the base commit, run this scenario's steps, still the screen, repeat with the worktree build. This comparison needs the base build, which lives outside the section worktree, hence advisory. (no capture cited it)
  • not proved live: ux-check: Bottom Actions rows: Feedback, Privacy choices and Sign out share the fern hue; Delete account's glyph remains destructive red, distinct from every fern/yellow-green glyph. (no capture cited it)
  • not proved live: ux-check: Bottom bar (Početna / Agenti / Profil): in both themes the active and inactive tabs remain legible and the bar does not visually compete with or contradict the coloured row icons above it. (no capture cited it)
  • not proved live: ux-check: Compare English and Serbian of the same screen and theme: for every one of the six rows the glyph colour and tile class are identical between languages (no colour changes with translation). (no capture cited it)
  • not proved live: iOS: not run — the diff forks on no platform, so Android proves both

e5-en-profile

e5-bottom

e2

Surface: mobile-app

The Profile screen's row icons have lost their colour and render plain white. Give them a curated,
deliberate palette, chosen once, in one shared place.

## The symptom, as reported

On the Profile screen (`Profil`), dark theme, Serbian locale, the row icons render plain white.
Affected rows: `Agent za pregled koda`, `Bezbednosni agent`, `Pregled PR-a`,
`Upravljaj organizacijom`, `Podešavanja`, `Tutorial`. Reproduce it before you change anything.

## The cause, confirmed in the source at `2f0d76d5d8f0`

All six rows are `ConfigureRow` (`apps/mobile/src/components/ui/configure-row.tsx`), used from
`apps/mobile/src/components/profile-screen.tsx:174-273`. `ConfigureRow` renders:

```tsx
const tint: Tint = tone ? toneColor(tone) : NEUTRAL_TINT;
...
const iconTile = <IconTile icon={Icon} tint={tint} />;
```

and `NEUTRAL_TINT` (`configure-row.tsx:36-42`) is:

```ts
{ tileBgClass: 'bg-hair-soft', tileBorderClass: 'border-border',
  hueThemeKey: 'secondaryForeground' }
```

`IconTile` (`apps/mobile/src/components/ui/icon-tile.tsx`) strokes the glyph with
`colors[tint.hueThemeKey]`, so every one of these rows paints `secondaryForeground` — near-white in
dark theme — on a `hair-soft` tile that is 5% alpha. A white glyph on an invisible tile is exactly
"lost their colour, plain white".

The file's own comment records why it is that way:

> A settings list is not a list of agents: hashing each title into the agent hue ramp gave a single
> list (Language / Trusted ho
@kilo-code-bot

kilo-code-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the curated Profile row-hue palette change (13 files) at ba88e5afc; the palette, prop wiring, precedence, generated tokens and tests are internally consistent with no correctness, security or regression defects found.

Overview

The change replaces label/title-hashed tints with an explicit RowHue prop (ConfigureRow precedence tone > hue > neutral, ActionTile requires a hue with destructive overriding to the danger tone), adds --row-* light/dark tokens kept in sync with the generated colour map, and adds a WCAG 1.4.11 non-text contrast assertion. Verified:

  • theme-colors.generated.ts values match global.css exactly (light and dark), so assert-theme-colors.mjs stays green.
  • Non-text contrast recomputed from the shipped hexes: every step clears 3:1 against both secondary and its 10%-alpha tinted tile in both themes (lowest is gold/light at ~3.41 vs tile; honey/light ~4.50 vs secondary), matching the PR table.
  • All ActionTile call sites pass hue, and the only satisfies ThemeColors literal (markdown-palette.test.ts) was updated for the new required keys.
  • The new mounted test's targets (findConfigureRows by title, ActionTile mocked as a host string, admin org role, prReviewEnabled) all resolve against the existing test harness.
  • No new subscriptions, timers, effects or listeners were introduced, so no memory-leak surface was added.
Files Reviewed (13 files)
  • apps/mobile/scripts/generate-theme-colors.mjs
  • apps/mobile/src/app/(app)/(tabs)/_layout.tsx - comment only
  • apps/mobile/src/components/agents/markdown-palette.test.ts
  • apps/mobile/src/components/profile-action-tile.mounted.test.tsx
  • apps/mobile/src/components/profile-action-tile.tsx
  • apps/mobile/src/components/profile-screen.queries.mounted.test.tsx
  • apps/mobile/src/components/profile-screen.tsx
  • apps/mobile/src/components/ui/configure-row.tsx
  • apps/mobile/src/components/ui/icon-tile.tsx - comment only
  • apps/mobile/src/global.css
  • apps/mobile/src/lib/agent-color.ts
  • apps/mobile/src/lib/hooks/theme-colors.generated.ts
  • apps/mobile/src/lib/hooks/use-theme-colors.contrast.test.ts

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic

iscekic commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator Author

This description names a scenario the proof did not capture:

  • not proved live: Dark theme (the reported symptom): switch Appearance to Dark in Preferences, return to Profile and still the dark screen. The light counterpart uses the same path with 'Light' and is captured by the verifier. (no capture cited it)

A repeated proof run rebuilds the same evidence, so no proof run is dispatched for a named gap. Merging with this gap open is your decision.

@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 23, 2026
@iscekic iscekic self-assigned this Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants