Skip to content

Add Event Details modal showing firmware settings and event info - #389

Merged
thebentern merged 4 commits into
mainfrom
claude/defcon-firmware-settings-sheet-qkb5z1
Jul 29, 2026
Merged

Add Event Details modal showing firmware settings and event info#389
thebentern merged 4 commits into
mainfrom
claude/defcon-firmware-settings-sheet-qkb5z1

Conversation

@rcarteraz

@rcarteraz rcarteraz commented Jul 29, 2026

Copy link
Copy Markdown
Member

Description

Adds an "Event Details" modal dialog that displays comprehensive information about event firmware builds, including:

  • Event information: Name, dates, location, timezone, and welcome message
  • Firmware details: Build name and version
  • LoRa configuration: Region, modem preset, frequency slot, hop limit, and MQTT ignore setting
  • Channels: Channel names, PSKs (displayed as base64), uplink status, and position precision
  • MQTT settings: Address, credentials, root topic, encryption, and TLS configuration
  • Event links: Curated links from the event manifest

The modal fetches and parses the userPrefs.jsonc file from the event firmware branch on GitHub to extract the radio settings baked into the build. This gives users visibility into exactly what configuration they'll get when flashing event firmware.

Implementation Details

  • EventDetails.vue: Modal component with lazy-loading of preferences and keyboard/serial monitor integration
  • eventUserPrefs.ts: Utilities for fetching and parsing the JSONC configuration file, including:
    • JSONC parser that strips comments and trailing commas
    • Byte array to base64 conversion for PSK display
    • Enum value prettification (e.g., SHORT_TURBOShort Turbo)
    • Full event preferences parsing
  • eventDates.ts: Date range formatting that respects locale and handles date-only strings correctly
  • useEventEdition.ts: Composable to access the full event manifest edition data
  • i18n: English translations for all event details labels and sections

The modal integrates with existing event mode infrastructure and automatically closes when the serial monitor connects.

Type of Change

  • New feature

Testing

  • Added comprehensive unit tests for JSONC parsing, byte array conversion, enum formatting, and date range formatting
  • All new tests pass
  • Modal integrates with existing event mode and serial monitor store

Summary by CodeRabbit

  • New Features

    • Added an Event Details button and modal for viewing event dates, location, timezone, welcome message, firmware information, and optional event links.
    • Added display of LoRa, channel, MQTT, and other radio settings when available.
    • Added a link to view the event’s source configuration.
    • Event details automatically close when the serial monitor connects.
  • Bug Fixes

    • Improved event date formatting across time zones and locales, including same-day events.

claude added 4 commits July 29, 2026 21:03
Adds an "Event Details" button to the main button row (event mode only,
e.g. the DEF CON flasher) that opens a dismissible sheet showing the
event firmware's details: event name, dates, location and time zone,
firmware build and version, theme name/tagline/palette, and official
event links. The sheet follows the existing pure-Vue dialog pattern
(AlphanautFeedback) and closes via the X button, backdrop click, or
Escape with focus restored to the trigger.

The full manifest edition (dates, location, links, firmware version) was
previously discarded after manifestEditionToEventMode(); it is now kept
in a new useEventEdition composable, set only alongside
setActiveEventMode so the displayed details never diverge from the
applied event mode. Event dates are date-only strings, so a small
formatEventDateRange util parses them as local dates (avoiding the UTC
off-by-one) and is covered by colocated Vitest tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWbctVTeY3iySKcELxhcR5
Event firmware is compiled from userPrefs.jsonc on the matching
event/<slug> branch of meshtastic/firmware. The Event Details sheet now
fetches that file lazily on first open (keyed off the edition's firmware
slug) and renders the settings flashers actually care about: LoRa
region, modem preset, frequency slot, hop limit and ignore-MQTT; each
channel with its name, base64 PSK, uplink flag and position precision;
and MQTT server settings — plus a link to the source file on GitHub.

A new utils/eventUserPrefs.ts holds the pure logic (string-aware JSONC
comment stripping, C byte-array → base64 PSK conversion, protobuf enum
prettifying, structured extraction) with colocated Vitest coverage,
verified against the real event/defcon34 file. Fetch failures degrade
to a muted "unavailable" note; static-fallback events without a
firmware slug skip the settings section entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWbctVTeY3iySKcELxhcR5
Drop the event, firmware-build, theme and links cards — the sheet now
shows just the settings baked into the event build (LoRa, channels with
PSKs, MQTT) plus the userPrefs.jsonc source link. Button and dialog are
retitled "Firmware Settings" / "Event Firmware Settings" with a settings
icon, and the now-unused date-range util and i18n keys are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWbctVTeY3iySKcELxhcR5
Bring back the event, firmware-build and links cards (and the date-range
util they use) alongside the radio settings — only the theme
name/tagline/palette card stays removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWbctVTeY3iySKcELxhcR5
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-flasher Ready Ready Preview Jul 29, 2026 10:13pm

Request Review

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an event details modal shown while disconnected, shared active edition state, locale-aware event date formatting, and lazy retrieval and parsing of firmware userPrefs.jsonc settings.

Changes

Event details flow

Layer / File(s) Summary
Active event edition state
composables/useEventEdition.ts, plugins/eventMode.client.ts
The resolved firmware edition is stored in shared reactive state alongside the active event mode.
User preferences parsing
utils/eventUserPrefs.ts, utils/eventUserPrefs.test.ts
Adds JSONC parsing, preference extraction, value conversion, GitHub URL builders, fetching, and coverage for LoRa, channels, MQTT, and helper behavior.
Event details modal and integration
utils/eventDates.ts, utils/eventDates.test.ts, components/EventDetails.vue, app.vue, i18n/locales/en.json
Adds localized event and firmware details, date formatting, lazy preference loading, modal focus and keyboard handling, connection-aware closing, and disconnected-state rendering.

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

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant EventDetails
  participant fetchEventUserPrefs
  participant GitHubRaw
  participant serialMonitorStore
  Visitor->>EventDetails: Open details modal
  EventDetails->>fetchEventUserPrefs: Fetch preferences by firmware slug
  fetchEventUserPrefs->>GitHubRaw: Request userPrefs.jsonc
  GitHubRaw-->>fetchEventUserPrefs: Return JSONC content
  fetchEventUserPrefs-->>EventDetails: Return parsed preferences
  serialMonitorStore-->>EventDetails: Report serial connection
  EventDetails->>EventDetails: Close modal
Loading

Possibly related PRs

Suggested reviewers: caveman99

Poem

A bunny found details beneath the mode,
With dates and channels neatly showed.
JSONC crumbs became settings bright,
A modal opened soft and light.
When serial wakes, it hops from sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly matches the main change: adding an Event Details modal for event firmware info and settings.
Description check ✅ Passed The description covers the feature, implementation details, and testing, and follows the required template structure.

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.

@rcarteraz
rcarteraz requested a review from thebentern July 29, 2026 22:18
@rcarteraz

Copy link
Copy Markdown
Member Author

@thebentern Took at stab at having claude add a button to view the event details including radio settings (as the def con folks were asking about it). Please review and feel free to scorch everything if it didn't do a good job at it. I'm not sure how to see the preview so I can't tell, the preview claude showed me looked good.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
composables/useEventEdition.ts (1)

9-15: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Exported ref is mutable; consider readonly().

useEventEdition() returns the raw writable ref. Any consumer could set .value directly, bypassing setActiveEventEdition and breaking the "set only alongside setActiveEventMode, so the two never diverge" invariant documented in plugins/eventMode.client.ts.

♻️ Proposed fix
-import { ref } from 'vue'
+import { readonly, ref } from 'vue'
 import type { EventFirmwareEdition } from '~/types/eventFirmware'
 ...
-export const useEventEdition = () => activeEventEdition
+export const useEventEdition = () => readonly(activeEventEdition)
🤖 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 `@composables/useEventEdition.ts` around lines 9 - 15, Make the ref returned by
useEventEdition readonly so consumers cannot mutate activeEventEdition.value
directly and bypass setActiveEventEdition. Preserve setActiveEventEdition as the
internal mutation path and keep the existing EventFirmwareEdition|null state and
API behavior.
components/EventDetails.vue (1)

20-25: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

No focus trap inside the modal.

Tab/Shift+Tab can move focus out of the dialog into background page content while open, since this is a plain div[role="dialog"] rather than a natively focus-trapped element. Consider a focus-trap composable (e.g. useFocusTrap from @vueuse/integrations) to keep keyboard focus contained while open.

As per coding guidelines, "Maintain keyboard and focus behavior for interactive elements; use semantic buttons/links and ARIA labels when appropriate."

🤖 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 `@components/EventDetails.vue` around lines 20 - 25, Update the EventDetails
modal container to use a focus-trap mechanism, such as VueUse’s useFocusTrap,
activated while the dialog is open and deactivated when it closes. Preserve the
existing dialog semantics and ensure initial focus is placed inside the modal.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@components/EventDetails.vue`:
- Around line 329-347: Update the preference-loading flow around loadPrefs so
cached prefs and prefsState are associated with the firmwareSlug they were
fetched for. When firmwareSlug changes, invalidate the previous loaded or error
state and refetch for the new slug, while preserving the loading guard for the
same slug and avoiding requests when no slug is available. Ensure the reactive
edition change triggers this reset and reload so prefs and prefsSourceUrl
reflect the active firmware.

In `@utils/eventDates.test.ts`:
- Around line 26-29: Strengthen the assertion in the “keeps the calendar day
regardless of the local time zone” test so it verifies the rendered day is 6,
rather than merely checking that the output contains the character “6.” Assert
the expected date text or otherwise inspect the day component, while preserving
the existing formatEventDateRange call.

---

Nitpick comments:
In `@components/EventDetails.vue`:
- Around line 20-25: Update the EventDetails modal container to use a focus-trap
mechanism, such as VueUse’s useFocusTrap, activated while the dialog is open and
deactivated when it closes. Preserve the existing dialog semantics and ensure
initial focus is placed inside the modal.

In `@composables/useEventEdition.ts`:
- Around line 9-15: Make the ref returned by useEventEdition readonly so
consumers cannot mutate activeEventEdition.value directly and bypass
setActiveEventEdition. Preserve setActiveEventEdition as the internal mutation
path and keep the existing EventFirmwareEdition|null state and API behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b3e9383-f1af-4f6e-8bd8-053e86da7c8a

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9404f and e701c00.

📒 Files selected for processing (9)
  • app.vue
  • components/EventDetails.vue
  • composables/useEventEdition.ts
  • i18n/locales/en.json
  • plugins/eventMode.client.ts
  • utils/eventDates.test.ts
  • utils/eventDates.ts
  • utils/eventUserPrefs.test.ts
  • utils/eventUserPrefs.ts

Comment on lines +329 to +347
const firmwareSlug = computed(() => edition.value?.firmware?.slug)
const prefsSourceUrl = computed(() => firmwareSlug.value ? eventUserPrefsSourceUrl(firmwareSlug.value) : undefined)
const hasLora = computed(() => !!prefs.value && (
prefs.value.region !== undefined
|| prefs.value.modemPreset !== undefined
|| prefs.value.frequencySlot !== undefined
|| prefs.value.hopLimit !== undefined
|| prefs.value.ignoreMqtt !== undefined
))
const hasMqtt = computed(() => !!prefs.value && Object.values(prefs.value.mqtt).some(v => v !== undefined))

async function loadPrefs() {
const slug = firmwareSlug.value
if (prefsState.value !== 'idle' || !slug) return
prefsState.value = 'loading'
const result = await fetchEventUserPrefs(slug)
prefs.value = result
prefsState.value = result ? 'loaded' : 'error'
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fetched prefs never refresh if the active edition changes.

loadPrefs()'s prefsState.value !== 'idle' guard means prefs/prefsSourceUrl are fetched once and cached for the component's lifetime. If edition (and thus firmwareSlug) later resolves to a different event — plausible given the "background API refresh" logic in plugins/eventMode.client.ts — the modal keeps showing stale radio settings (or a stale error state) instead of refetching for the new slug.

♻️ Proposed fix
+watch(firmwareSlug, () => {
+  prefs.value = null
+  prefsState.value = 'idle'
+})
+
 async function loadPrefs() {
   const slug = firmwareSlug.value
   if (prefsState.value !== 'idle' || !slug) return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const firmwareSlug = computed(() => edition.value?.firmware?.slug)
const prefsSourceUrl = computed(() => firmwareSlug.value ? eventUserPrefsSourceUrl(firmwareSlug.value) : undefined)
const hasLora = computed(() => !!prefs.value && (
prefs.value.region !== undefined
|| prefs.value.modemPreset !== undefined
|| prefs.value.frequencySlot !== undefined
|| prefs.value.hopLimit !== undefined
|| prefs.value.ignoreMqtt !== undefined
))
const hasMqtt = computed(() => !!prefs.value && Object.values(prefs.value.mqtt).some(v => v !== undefined))
async function loadPrefs() {
const slug = firmwareSlug.value
if (prefsState.value !== 'idle' || !slug) return
prefsState.value = 'loading'
const result = await fetchEventUserPrefs(slug)
prefs.value = result
prefsState.value = result ? 'loaded' : 'error'
}
const firmwareSlug = computed(() => edition.value?.firmware?.slug)
const prefsSourceUrl = computed(() => firmwareSlug.value ? eventUserPrefsSourceUrl(firmwareSlug.value) : undefined)
const hasLora = computed(() => !!prefs.value && (
prefs.value.region !== undefined
|| prefs.value.modemPreset !== undefined
|| prefs.value.frequencySlot !== undefined
|| prefs.value.hopLimit !== undefined
|| prefs.value.ignoreMqtt !== undefined
))
const hasMqtt = computed(() => !!prefs.value && Object.values(prefs.value.mqtt).some(v => v !== undefined))
watch(firmwareSlug, () => {
prefs.value = null
prefsState.value = 'idle'
})
async function loadPrefs() {
const slug = firmwareSlug.value
if (prefsState.value !== 'idle' || !slug) return
prefsState.value = 'loading'
const result = await fetchEventUserPrefs(slug)
prefs.value = result
prefsState.value = result ? 'loaded' : 'error'
}
🤖 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 `@components/EventDetails.vue` around lines 329 - 347, Update the
preference-loading flow around loadPrefs so cached prefs and prefsState are
associated with the firmwareSlug they were fetched for. When firmwareSlug
changes, invalidate the previous loaded or error state and refetch for the new
slug, while preserving the loading guard for the same slug and avoiding requests
when no slug is available. Ensure the reactive edition change triggers this
reset and reload so prefs and prefsSourceUrl reflect the active firmware.

Comment thread utils/eventDates.test.ts
Comment on lines +26 to +29
it('keeps the calendar day regardless of the local time zone', () => {
// A UTC-midnight parse would render Aug 6 as Aug 5 anywhere west of UTC.
expect(formatEventDateRange('2026-08-06')).toContain('6')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assertion doesn't actually test the timezone-safety it claims to.

"Aug 5, 2026" (the buggy output this test is meant to catch) also contains '6' — from the year. .toContain('6') passes whether the day renders as 5 or 6, so this test can't detect the regression described in its own comment.

🐛 Proposed fix
-    expect(formatEventDateRange('2026-08-06')).toContain('6')
+    expect(formatEventDateRange('2026-08-06')).toContain('Aug 6')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('keeps the calendar day regardless of the local time zone', () => {
// A UTC-midnight parse would render Aug 6 as Aug 5 anywhere west of UTC.
expect(formatEventDateRange('2026-08-06')).toContain('6')
})
it('keeps the calendar day regardless of the local time zone', () => {
// A UTC-midnight parse would render Aug 6 as Aug 5 anywhere west of UTC.
expect(formatEventDateRange('2026-08-06')).toContain('Aug 6')
})
🤖 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 `@utils/eventDates.test.ts` around lines 26 - 29, Strengthen the assertion in
the “keeps the calendar day regardless of the local time zone” test so it
verifies the rendered day is 6, rather than merely checking that the output
contains the character “6.” Assert the expected date text or otherwise inspect
the day component, while preserving the existing formatEventDateRange call.

@thebentern
thebentern merged commit b48aadb into main Jul 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants