Skip to content

feat(event): DEF CON 34 logo and full brand palette in event theming - #6483

Merged
jamesarich merged 2 commits into
mainfrom
claude/defcon-firmware-branding-356ad4
Jul 28, 2026
Merged

feat(event): DEF CON 34 logo and full brand palette in event theming#6483
jamesarich merged 2 commits into
mainfrom
claude/defcon-firmware-branding-356ad4

Conversation

@jamesarich

@jamesarich jamesarich commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

🌟 Why

Event branding only ever read one color per edition: the accent wash behind the app bar. For DEF CON 34 that color is #0D294A — a navy dark enough that washed at 12% over a dark surface it is invisible. Connected to DEF CON firmware, the app showed a generic Meshtastic logo and no visible theming at all.

The metadata was never the problem. event_firmware.json already carries DEF CON's six-color palette, its named brand colors, the theme name ("Agency"), and the tagline. None of it reached the UI.

🛠️ What

Logo — bundles the DEF CON 34 mark as img_event_defcon.png (384×384, 18.6 KB) and registers it in eventIconFor(), and points the edition's iconUrl at the hosted copy shipping in meshtastic/api#109. Hosted first, bundled as the offline fallback — the existing EventBrandingIcon contract, so the icon appears whether or not the API change has landed.

PaletteEventTheme now carries the full palette and a high-contrast highlight alongside the wash accent. The palette is drawn as a gradient rule under the app bar and as a strip closing the info-sheet header. A single wash color can never show more than one color; the rule is where the rest of the palette lives.

Unused metadata — the info sheet now renders the theme name (as a subtitle to the event name) and the tagline.

Contrast — new pickLegible() in ColorContrast.kt. Brand palettes are authored for the event's own print and web materials, not for our light and dark surfaces: DEF CON's navy fails on dark, FAB26's #EAB14B fails on light. Info-sheet tints now walk highlight → palette → onSurfaceVariant, taking the first that clears WCAG AA for graphical objects.

Everything is data-driven off the manifest, so every edition gets whatever colors it publishes. DEF CON is just the first with a full palette to spend; Hamvention and Open Sauce publish one color and render exactly as before.

🎨 Design notes

  • The header band stays a single flat color so the title keeps a predictable contrast ratio. The palette gets its own strip rather than running under text.
  • The brand rule is 3 dp under the app bar, 6 dp in the sheet — brand presence without spending vertical space.
  • An edition with one color draws a flat rule instead of a gradient; an edition with none draws nothing.
  • All of it stays behind the existing "Use event theme" opt-out.

🧪 Testing performed

  • EventBrandingTest — new coverage for brandPalette() (authored palette preferred, malformed hex dropped, falls back to named colors then accent, de-duplicated, empty when nothing is published) and brandHighlightOrNull() (accent → secondary; a bare primary is the wash color and is never promoted to a highlight).
  • PickLegibleTest — new. Same palette resolving to different colors on light vs dark backgrounds, threshold honoured, fallback when nothing reads.
  • ./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests — green.

Not yet verified against a physically connected DEF CON device; the branding path itself (hosted icon → bundled fallback → Meshtastic logo) is unchanged, and the palette work is exercised by unit tests.

🔗 Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added DEF CON 34 event icon and richer event theming, including a color palette strip and theme subtitle in the event header.
    • Event details screens now use event-specific palette/highlight colors for improved visual branding, including a consistent theme toggle row.
    • Event themes now support highlights, palettes, and optional custom fonts.
  • Bug Fixes
    • Improved text/icon/link color legibility by selecting colors that meet contrast requirements across light and dark backgrounds.
  • Tests
    • Added coverage for legible color selection and event palette/highlight derivation.

Event branding read only one color per edition — the accent wash on the app
bar. For DEF CON 34 that color is #0D294A, a navy so dark it is invisible
washed over a dark surface, so the app showed a generic Meshtastic logo and no
visible theming at all while connected to DEF CON firmware. The metadata
already carried a six-color palette, named brand colors, a theme name, and a
tagline; none of it reached the UI.

- Bundle the DEF CON 34 mark as the branding icon, and point the edition's
  iconUrl at the hosted copy shipping alongside in meshtastic/api.
- Carry the full palette and a high-contrast highlight through EventTheme, and
  draw the palette as a gradient rule under the app bar and closing the info
  sheet header — the wash can only ever show one color.
- Surface the theme name and tagline in the info sheet ("Agency" for DC34).
- Pick info-sheet tints with pickLegible(): brand palettes are authored for the
  event's own materials, so an edition's loudest color may fail contrast on our
  light or dark surface. Falls back through the palette, then onSurfaceVariant.

All of it is data-driven, so every edition gets whatever colors it publishes;
DEF CON is simply the first with a full palette to spend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the enhancement New feature or request label Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 23e8314c-a23c-434d-9242-af0875420f5c

📥 Commits

Reviewing files that changed from the base of the PR and between bbc0bae and a679d2b.

📒 Files selected for processing (5)
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventPaletteStrip.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/EventBrandingTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt

📝 Walkthrough

Walkthrough

DEF CON 34 metadata now includes an icon URL. Event branding resolves palettes and highlights, propagates them through EventTheme, and renders them in the app bar and event information sheet with contrast-aware icon colors.

Changes

Event branding

Layer / File(s) Summary
Branding data and contrast contracts
androidApp/src/main/assets/event_firmware.json, core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt, core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/theme/*, core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/{theme,util}/*
DEF CON imagery, color parsing, palette/highlight resolution, EventTheme fields, contrast selection, and corresponding tests are added.
Application event theme wiring
androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
The active event’s highlight and memoized palette are passed into EventTheme.
Branded app bar and event sheet
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/{MainAppBar,EventInfoSheet,EventPaletteStrip}.kt
The app bar and event sheet render palette strips, while event details select legible icon tints. Existing event details and theme opt-out interactions remain wired.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EventFirmwareEdition
  participant AppCompositionLocals
  participant LocalEventTheme
  participant MainAppBar
  participant EventInfoSheet
  EventFirmwareEdition->>AppCompositionLocals: provide event metadata
  AppCompositionLocals->>LocalEventTheme: resolve accent, highlight, and palette
  LocalEventTheme->>MainAppBar: provide palette
  LocalEventTheme->>EventInfoSheet: provide palette and highlight
  MainAppBar->>MainAppBar: render brand rule
  EventInfoSheet->>EventInfoSheet: render palette strip and legible icons
Loading

Possibly related issues

  • meshtastic/design 120 — Covers the event firmware metadata and branding paths modified here.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: DEF CON 34 branding plus full brand palette support in event theming.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt (1)

127-132: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Authored palette path skips distinct().

The fallback branch de-dupes but fromPalette doesn't, so a duplicated authored hex yields repeated gradient stops. Cheap to make consistent.

♻️ Proposed tweak
-    val fromPalette = theme?.palette.orEmpty().mapNotNull(::parseBrandColor)
+    val fromPalette = theme?.palette.orEmpty().mapNotNull(::parseBrandColor).distinct()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt`
around lines 127 - 132, Update EventFirmwareEdition.brandPalette so the authored
theme palette mapped through parseBrandColor is de-duplicated with distinct()
before returning, matching the fallback named-color path while preserving
palette order.
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt (1)

155-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Palette-strip rendering is duplicated across two files. Both sites build the same gradient-or-solid Brush and draw the same full-width Box, each with its own private MIN_GRADIENT_STOPS; only the height differs. Extract one shared composable (e.g. EventPaletteStrip(palette, height)) in core:ui and drop the duplicate constant.

  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt#L155-L165: replace EventBrandRule with a call to the shared composable using EVENT_BRAND_RULE_HEIGHT, and remove the local MIN_GRADIENT_STOPS.
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt#L207-L217: replace the inline strip block with the shared composable using PALETTE_STRIP_HEIGHT, and remove the local MIN_GRADIENT_STOPS.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt`
around lines 155 - 165, Extract the duplicated palette-strip rendering into a
shared EventPaletteStrip composable in core/ui, centralizing the gradient/solid
Brush logic and MIN_GRADIENT_STOPS constant. In
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt#L155-L165,
replace EventBrandRule with EventPaletteStrip using EVENT_BRAND_RULE_HEIGHT and
remove the local constant; in
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt#L207-L217,
replace the inline strip with EventPaletteStrip using PALETTE_STRIP_HEIGHT and
remove its local constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt`:
- Around line 155-165: Extract the duplicated palette-strip rendering into a
shared EventPaletteStrip composable in core/ui, centralizing the gradient/solid
Brush logic and MIN_GRADIENT_STOPS constant. In
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt#L155-L165,
replace EventBrandRule with EventPaletteStrip using EVENT_BRAND_RULE_HEIGHT and
remove the local constant; in
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt#L207-L217,
replace the inline strip with EventPaletteStrip using PALETTE_STRIP_HEIGHT and
remove its local constant.

In
`@core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt`:
- Around line 127-132: Update EventFirmwareEdition.brandPalette so the authored
theme palette mapped through parseBrandColor is de-duplicated with distinct()
before returning, matching the fallback named-color path while preserving
palette order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d74686c6-d1b8-4260-95e7-0c51a6e2f562

📥 Commits

Reviewing files that changed from the base of the PR and between b806a4d and bbc0bae.

⛔ Files ignored due to path filters (1)
  • core/resources/src/commonMain/composeResources/drawable/img_event_defcon.png is excluded by !**/*.png, !**/*.png
📒 Files selected for processing (9)
  • androidApp/src/main/assets/event_firmware.json
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/EventInfoSheet.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/MainAppBar.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/theme/ColorContrast.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/theme/EventFonts.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalEventBranding.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/theme/PickLegibleTest.kt
  • core/ui/src/commonTest/kotlin/org/meshtastic/core/ui/util/EventBrandingTest.kt

…olors

Addresses CodeRabbit nitpicks on #6483.

The app bar rule and the info-sheet header strip built the same
gradient-or-solid brush and drew the same full-width Box, each with its own
private MIN_GRADIENT_STOPS; only the height differed. Extract EventPaletteStrip
and pass the height.

brandPalette() de-duplicated the named-color fallback but not the authored
palette, so a repeated hex became a repeated gradient stop — flattening the
gradient over that span. Both paths now de-dupe, preserving authored order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

Both CodeRabbit nitpicks addressed in a679d2b:

  • Shared palette strip — extracted EventPaletteStrip(palette, height) into core:ui; MainAppBar and EventInfoSheet both call it, and both copies of the private MIN_GRADIENT_STOPS are gone.
  • distinct() on the authored palette — a repeated hex was becoming a repeated gradient stop, flattening the gradient over that span. Both paths now de-dupe, preserving authored order. Covered by a new brandPaletteDeDupesAuthoredEntriesPreservingOrder test.

No inline review threads were opened on this PR, so there was nothing to reply to or resolve individually.

🤖 Addressed by Claude Code

@jamesarich
jamesarich merged commit 6872381 into main Jul 28, 2026
15 checks passed
@jamesarich
jamesarich deleted the claude/defcon-firmware-branding-356ad4 branch July 28, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant