Skip to content

Move hardcoded view strings to models and adopt playolaAlert#316

Open
briankeane wants to merge 8 commits into
developfrom
briankeane/view-string-and-alert-cleanup
Open

Move hardcoded view strings to models and adopt playolaAlert#316
briankeane wants to merge 8 commits into
developfrom
briankeane/view-string-and-alert-cleanup

Conversation

@briankeane

@briankeane briankeane commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Moves ~95 hardcoded user-facing strings (nav titles, labels, button text, empty-state copy, placeholders) out of 24 page views into computed properties on their @Observable models, so the model owns all display text per the MV architecture. Also swaps raw .alert(item: $model.presentedAlert) { $0.alert } for the project's .playolaAlert($model.presentedAlert) modifier wherever it's behavior-preserving. RecordPage, AskQuestionPage, and ListenerQuestionDetailPage keep the legacy .alert because their alerts carry a dismissButton action or a secondary button, which .playolaAlert's button builder does not yet support (it would render an inert OK and drop the action). PlayerPage is intentionally left for a follow-up PR (it also needs a UIScreen.main layout migration). Behavior is unchanged: full test suite passes and an independent Codex review confirmed no regressions.

Summary by CodeRabbit

  • New Features
    • Improved clarity and consistency of in-app text across many screens (titles, instructions, button labels, placeholders, empty states) spanning profiles, support, rewards, search, recording, broadcasting, series browsing, and questions.
  • Bug Fixes
    • Standardized alert presentation for a more consistent experience.
    • Refined display wording and formatting, including recording/review labels, “at ” schedule times, and pluralized vote counts.
  • Refactor
    • Centralized user-facing copy by sourcing visible labels from each screen’s view model for uniform UI text.

playolaAlert's button builder renders an inert OK and drops dismissButton
actions and secondary buttons. RecordPage (Settings button), AskQuestionPage
and ListenerQuestionDetailPage (onDismiss) rely on those, so they keep the
legacy alert until PlayolaAlert gains a single-button-with-action form.
@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts ~95 hardcoded user-facing strings from 24 view files into computed properties on their corresponding @Observable models, and replaces most raw .alert(item:) calls with the project's .playolaAlert modifier across 14 views.

  • String extraction: Every changed view now sources its labels, titles, button text, and empty-state copy from model properties. BroadcastPageModel also promotes its DateFormatter to a private static property, fixing a pre-existing per-call allocation.
  • RecordPage logic fixed: The @ViewBuilder switch on recordingPhase in RecordPageView.buttonLabelSection has been moved to RecordPageModel.currentButtonLabel, fully satisfying the no-logic-in-views rule for that page.
  • Partial fix in BroadcastersListenerQuestionPage: expandToggleText was correctly moved to the model, but ListenerQuestionRow.transcription and .listenerName still contain nil-coalescing computed properties in the view.

Confidence Score: 4/5

Safe to merge with one follow-up: ListenerQuestionRow still contains nil-coalescing computed properties in the view that should be moved to the model.

The vast majority of changes are mechanical string lifts with no behavioral impact. The one gap is in BroadcastersListenerQuestionPageView — ListenerQuestionRow.transcription and listenerName still perform nil-coalescing in the view, which is the same pattern RecordPage correctly fixed in this PR. Everything else (string extraction, static DateFormatter, playolaAlert adoption, RecordPage logic move) is clean and consistent with the project's architecture.

PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift — ListenerQuestionRow still has conditional display logic in the view.

Important Files Changed

Filename Overview
PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift Moves strings to model and adopts .playolaAlert, but ListenerQuestionRow still contains nil-coalescing computed properties (transcription, listenerName) that violate no-logic-in-views; expandToggleText was correctly moved to the model.
PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageModel.swift Adds well-structured display-text properties and the expandToggleText(for:) helper method; navigation title renamed from 'Questions from Listeners' to 'Listener Questions'.
PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageModel.swift Adds display-text properties and fixes DateFormatter allocation by promoting the formatter to a private static property; airtimeLabel(for:) correctly handles lowercased format.
PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift Adds MARK: - View Helpers section with all display-text properties including currentButtonLabel that consolidates the phase switch previously in the view.
PlayolaRadio/Views/Pages/RecordPage/RecordPageView.swift Removes the @ViewBuilder switch on recordingPhase from buttonLabelSection and replaces it with a single Text(model.currentButtonLabel); all hardcoded strings removed.
PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageModel.swift Adds voteCountLabel with correct pluralization logic, but this new method has no unit test; voteCountText (now unused by the view) still has a test in StationSuggestionPageTests.
PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift Reorders MARK sections (User Actions now before View Helpers per spec), adds display-text properties, and correctly handles hour/hours pluralization in prizeTierHoursToGoText.
PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageView.swift NowPlayingContentView and ScheduleRowView now require string parameters passed from the model; .alert replaced with .playolaAlert; no other callers of these structs exist in the codebase.
PlayolaRadio/Views/Pages/MainContainer/MainContainerModel.swift Adds tab title properties for all 8 tabs; straightforward extraction with no issues.
PlayolaRadio/Views/Pages/AskQuestionPage/AskQuestionPageModel.swift Adds display-text properties; static string constants use let rather than var computed properties (minor style inconsistency vs other models in this PR, but functionally correct).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    subgraph View["View (layout + rendering only)"]
        T["Text(model.label)"]
        B["Button(model.buttonTitle)"]
        A[".playolaAlert($model.presentedAlert)"]
    end

    subgraph Model["@Observable Model (all content)"]
        NL["var navigationTitle: String"]
        BL["var buttonTitle: String"]
        CH["func airtimeLabel(for:) -> String"]
        ET["func expandToggleText(for:) -> String"]
        PA["var presentedAlert: PlayolaAlert?"]
    end

    NL -->|string| T
    BL -->|string| B
    CH -->|string| T
    ET -->|string| T
    PA -->|binding| A
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart LR
    subgraph View["View (layout + rendering only)"]
        T["Text(model.label)"]
        B["Button(model.buttonTitle)"]
        A[".playolaAlert($model.presentedAlert)"]
    end

    subgraph Model["@Observable Model (all content)"]
        NL["var navigationTitle: String"]
        BL["var buttonTitle: String"]
        CH["func airtimeLabel(for:) -> String"]
        ET["func expandToggleText(for:) -> String"]
        PA["var presentedAlert: PlayolaAlert?"]
    end

    NL -->|string| T
    BL -->|string| B
    CH -->|string| T
    ET -->|string| T
    PA -->|binding| A
Loading

Reviews (4): Last reviewed commit: "Move show more/less label selection into..." | Re-trigger Greptile

Comment thread PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift
- RecordPageView: collapse phase switch into model.currentButtonLabel so the
  view holds no conditional display logic.
- BroadcastPageModel: hoist airtimeLabel's DateFormatter to a reused static
  property instead of allocating one per call.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6d28be9e-890a-4340-b31e-0491c80dfa48

📥 Commits

Reviewing files that changed from the base of the PR and between 9d01bca and a8436bd.

📒 Files selected for processing (2)
  • PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageModel.swift
  • PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift

📝 Walkthrough

Walkthrough

UI strings now come from page models and reusable components instead of literals. Several screens also switched alert presentation to .playolaAlert($model.presentedAlert), and some row views now receive injected display text from their callers.

Changes

UI String Centralization and Alert Migration

Layer / File(s) Summary
View model string properties across all pages
PlayolaRadio/Views/Pages/AskQuestionPage/AskQuestionPageModel.swift, PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageModel.swift, PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageModel.swift, PlayolaRadio/Views/Pages/ContactPage/ContactPageModel.swift, PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageModel.swift, PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageModel.swift, PlayolaRadio/Views/Pages/HomePage/HomePageModel.swift, PlayolaRadio/Views/Pages/ListenerQuestionDetailPage/ListenerQuestionDetailPageModel.swift, PlayolaRadio/Views/Pages/MainContainer/MainContainerModel.swift, PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageModel.swift, PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift, PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift, PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift, PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCardModel.swift, PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPageModel.swift, PlayolaRadio/Views/Pages/SignInPage/SignInPageModel.swift, PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageModel.swift, PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageModel.swift, PlayolaRadio/Views/Pages/SupportPage/SupportPageModel.swift, PlayolaRadio/Views/Reusable Components/ListeningTimeTile/ListeningTimeTileModel.swift
Computed string properties were added for titles, button labels, placeholders, empty states, and helpers such as recordButtonLabel, currentButtonLabel, airtimeLabel(for:), and prizeTierHoursToGoText(for:).
Views and components consume model text
PlayolaRadio/Views/Pages/AskQuestionPage/AskQuestionPageView.swift, PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageView.swift, PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift, PlayolaRadio/Views/Pages/ContactPage/ContactPageView.swift, PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageView.swift, PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageView.swift, PlayolaRadio/Views/Pages/HomePage/HomePageView.swift, PlayolaRadio/Views/Pages/HomePage/UI Components/CircleSection.swift, PlayolaRadio/Views/Pages/HomePage/UI Components/HomePageStationList.swift, PlayolaRadio/Views/Pages/ListenerQuestionDetailPage/ListenerQuestionDetailPageView.swift, PlayolaRadio/Views/Pages/MainContainer/MainContainer.swift, PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageView.swift, PlayolaRadio/Views/Pages/RecordPage/RecordPageView.swift, PlayolaRadio/Views/Pages/RewardsPage/PrizeTierRow.swift, PlayolaRadio/Views/Pages/RewardsPage/RewardsPageView.swift, PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRow.swift, PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCard.swift, PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPage.swift, PlayolaRadio/Views/Pages/SignInPage/SignInPageView.swift, PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift, PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageView.swift, PlayolaRadio/Views/Pages/SupportPage/SupportPageView.swift, PlayolaRadio/Views/Reusable Components/ListeningTimeTile/ListeningTimeTile.swift
Views and reusable components now read model-provided text for titles, prompts, badges, placeholders, and button labels. Several child views add injected string parameters such as liveNowLabel, airtimeLabel, introMessage, title, and buttonText.
Alert presentation migrated to .playolaAlert
PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageView.swift, PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift, PlayolaRadio/Views/Pages/ContactPage/ContactPageView.swift, PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageView.swift, PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageView.swift, PlayolaRadio/Views/Pages/FeedbackSheet/FeedbackSheetView.swift, PlayolaRadio/Views/Pages/HomePage/HomePageView.swift, PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageView.swift, PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPage.swift, PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift, PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageView.swift, PlayolaRadio/Views/Pages/SupportPage/SupportPageView.swift
These screens now present model.presentedAlert through .playolaAlert($model.presentedAlert) instead of SwiftUI .alert(item:).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through titles, neat and bright,
The models hum the words just right.
Alerts now bloom in one clean way,
And buttons chirp the labels all day.
A carrot-bright refactor, tidy and sweet! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Summarizes the main change: moving UI strings into models and replacing standard alerts with playolaAlert.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch briankeane/view-string-and-alert-cleanup

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

@briankeane

Copy link
Copy Markdown
Contributor Author

@greptile review this

@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: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
PlayolaRadio/Views/Pages/HomePage/UI Components/CircleSection.swift (1)

10-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make introMessage required instead of keeping fallback copy in the view.

The new parameterization is good, but Line 10 still leaves the old user-facing string embedded in the component. That makes it easy for a future call site to skip injection and drift away from model-owned copy again.

Suggested change
-  var introMessage: String = "Discover music through independent artist made radio stations."
+  let introMessage: String
🤖 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 `@PlayolaRadio/Views/Pages/HomePage/UI` Components/CircleSection.swift around
lines 10 - 26, Make CircleSection’s introMessage required instead of defaulting
to embedded fallback copy. Update the CircleSection view definition so the
caller must supply introMessage (alongside onIconTapped10Times), and remove the
hardcoded user-facing string from the property declaration to keep the text
source externalized and consistent with the rest of the UI.
PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift (1)

81-93: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move the seed-row action branching into the model.

This button still decides in the view whether to call onAddSongToLibrary or onRequestSong. Expose one model action for this tap and call only that from the view.

♻️ Suggested direction
                   SongRequestResultRow(
                     songRequest: songRequest,
                     buttonText: model.songSeedActionButtonText,
                     isProcessing: model.isProcessingAdd(for: songRequest)
                   ) {
-                    Task {
-                      if model.isLibraryAddMode {
-                        await model.onAddSongToLibrary(songRequest)
-                      } else {
-                        await model.onRequestSong(songRequest)
-                      }
-                    }
+                    Task { await model.songSeedActionButtonTapped(songRequest) }
                   }
As per path instructions, `**/*View.swift`: SwiftUI views should call model methods for all user actions and avoid logic in the view.
🤖 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 `@PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift` around
lines 81 - 93, The SongSearchPageView still contains tap-time branching between
onAddSongToLibrary and onRequestSong, so move that decision into the
SongSearchPageViewModel by exposing a single action for the seed-row tap. Update
the SongRequestResultRow callback to call only that model method, and have the
model internally route to the correct behavior based on isLibraryAddMode while
preserving the existing button text and processing state.

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
`@PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift`:
- Around line 155-159: Remove the hardcoded fallback text defaults from
ListenerQuestionRow so the view no longer owns any user-facing strings. Make the
text properties required inputs, and update the initializer/call sites to pass
model-owned values for answeredBadgeText, missingTranscriptionText,
unknownListenerText, showMoreText, and showLessText. Keep the view focused on
layout and presentation only, using the existing ListenerQuestionRow symbols to
locate and adjust the bindings.

In `@PlayolaRadio/Views/Pages/ContactPage/ContactPageModel.swift`:
- Around line 75-83: The ContactPageModel property block is missing the required
page-model organization and should be placed under the prescribed // MARK:
sections. Update ContactPageModel to restore the standard order by adding the
missing Initialization, User Actions, View Helpers, and Private Helpers sections
and moving the label/title properties into the correct Properties section while
keeping the existing Dependencies and Shared State structure intact.

In
`@PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageModel.swift`:
- Around line 22-24: The ConversationListPageModel file is missing the required
section organization, so add the prescribed // MARK: sections in the expected
order and place the existing display-string properties under the proper
Properties section. Update ConversationListPageModel to follow the repository’s
page model layout: Dependencies, Shared State, Initialization, Properties, User
Actions, View Helpers, then Private Helpers, keeping navigationTitle and
emptyStateMessage within Properties.

In `@PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageModel.swift`:
- Around line 28-35: The EditProfilePageModel is adding display-text properties
without the required section organization. Move these properties under the
existing prescribed `// MARK: - Properties` area and ensure the file follows the
standard order for page models: Dependencies, Shared State, Initialization,
Properties, User Actions, View Helpers, then Private Helpers. Use the
EditProfilePageModel symbol to keep the structure consistent as you touch this
model.

In `@PlayolaRadio/Views/Pages/HomePage/UI` Components/HomePageStationList.swift:
- Around line 85-93: The HomePageStationList component still exposes a hardcoded
fallback title via its title property, which lets call sites bypass model-owned
copy; remove the default value and require title to be passed in explicitly.
Update the HomePageStationList view API so title is a required dependency
alongside stations, liveStatusForStation, hasUpcomingGiveawayForStation, and
onRadioStationSelected, keeping the header text supplied by the parent/model.

In
`@PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageModel.swift`:
- Around line 64-69: The new display-text properties in
NotificationsSettingsPageModel are not placed under the required page-model
section structure. Move these computed properties into the `// MARK: -
Properties` section and ensure the file follows the prescribed order for
`NotificationsSettingsPageModel` (Dependencies, Shared State, Initialization,
Properties, User Actions, View Helpers, Private Helpers) so the new
titles/subtitles are grouped correctly.

In `@PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift`:
- Around line 59-84: The display helper properties in RecordPageModel need to be
moved into the standard page-model section layout. Reorder and rename the
surrounding MARK sections in RecordPageModel so the file follows the required
sequence: Dependencies, Shared State, Initialization, Properties, User Actions,
View Helpers, then Private Helpers. Keep the helper properties like
navigationTitle, doneButtonTitle, waveformPlaceholderText, recordingStatusText,
recordButtonLabel, stopButtonLabel, reRecordButtonLabel, currentButtonLabel,
discardButtonTitle, and useRecordingButtonTitle grouped under the View Helpers
section.

In `@PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift`:
- Around line 75-86: The RewardsPageModel file has page-model sections out of
the required order, with the new display properties/helpers placed under the
wrong MARK grouping. Reorganize the surrounding section labels and members in
RewardsPageModel so it follows the prescribed order: Dependencies, Shared State,
Initialization, Properties, User Actions, View Helpers, then Private Helpers.
Make sure navigationTitle, yourRewardsTitle, yourRewardsSubtitle, and
prizeTierHoursToGoText are placed under the correct section for view
helpers/properties per the repository convention.
- Around line 81-84: The countdown text in
RewardsPageModel.prizeTierHoursToGoText(for:) always pluralizes “hours,” so
update the formatting to handle the singular case when hoursToGo is 1. Keep the
logic in prizeTierHoursToGoText(for:) and return “1 hour to go” for one hour,
while preserving the existing plural text for all other values.

In `@PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift`:
- Around line 45-48: The page model is missing the required // MARK section
structure, so update EpisodeRowModel to follow the prescribed order:
Dependencies, Shared State, Initialization, Properties, User Actions, View
Helpers, then Private Helpers. Move the new display-facing helpers like
episodeTitle and originallyAiredText into the View Helpers section and keep the
file organized consistently with the other page models. Use the existing
EpisodeRowModel symbol to place the properties in the right section and align
the header layout before adding more helpers.

In `@PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCardModel.swift`:
- Around line 28-39: The SeriesCardModel display properties were added without
the required page-model MARK organization, so reorganize this type to match the
repo’s standard section order. In SeriesCardModel, move the new computed UI
fields into the proper // MARK sections and ensure the file follows
Dependencies, Shared State, Initialization, Properties, User Actions, View
Helpers, then Private Helpers, with these display-only getters grouped under
View Helpers rather than left as unsectioned helpers.

In `@PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPageModel.swift`:
- Around line 28-29: The SeriesListPageModel file still uses the old
State/Init/Actions organization, so restore it to the required page-model layout
while keeping navigationTitle in the correct section. Reorder the type into the
prescribed // MARK: sections in this order: Dependencies, Shared State,
Initialization, Properties, User Actions, View Helpers, then Private Helpers,
and place navigationTitle under View Helpers since it is only a display helper.
Use the SeriesListPageModel symbol to update the structure without changing
behavior.

In `@PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageModel.swift`:
- Around line 67-74: The display helper properties in SongSearchPageModel are in
the wrong place and the page-model is missing the required // MARK: section
structure. Move these view-facing strings, including the conditional
songSeedActionButtonText, under the View Helpers section in SongSearchPageModel,
and ensure the file follows the prescribed section order: Dependencies, Shared
State, Initialization, Properties, User Actions, View Helpers, then Private
Helpers.

In `@PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift`:
- Around line 142-145: SongSearchResultRow is still using a hardcoded fallback
for buttonText, which allows user-facing copy to come from the view instead of
the caller/model. Make buttonText a required input on SongSearchResultRow and
update any call sites in SongSearchPageView or related builders to pass the
label explicitly, removing the default "SELECT" so the view no longer supplies
its own copy.

In
`@PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageModel.swift`:
- Around line 127-129: The vote label in voteCountLabel is always using the
plural suffix, which produces incorrect text for a single vote. Update
voteCountLabel to keep using voteCountText but choose the suffix based on
suggestion.voteCount so it renders “vote” when the count is 1 and “votes”
otherwise.

In `@PlayolaRadio/Views/Pages/SupportPage/SupportPageModel.swift`:
- Around line 36-38: The SupportPageModel is missing the required page-model
organization blocks, so add the prescribed // MARK: sections in the expected
order and place the new display-text properties under the Properties section.
Use the SupportPageModel type as the anchor, and ensure the file is structured
with Dependencies, Shared State, Initialization, Properties, User Actions, View
Helpers, then Private Helpers so it matches the coding guidelines.

---

Outside diff comments:
In `@PlayolaRadio/Views/Pages/HomePage/UI` Components/CircleSection.swift:
- Around line 10-26: Make CircleSection’s introMessage required instead of
defaulting to embedded fallback copy. Update the CircleSection view definition
so the caller must supply introMessage (alongside onIconTapped10Times), and
remove the hardcoded user-facing string from the property declaration to keep
the text source externalized and consistent with the rest of the UI.

In `@PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift`:
- Around line 81-93: The SongSearchPageView still contains tap-time branching
between onAddSongToLibrary and onRequestSong, so move that decision into the
SongSearchPageViewModel by exposing a single action for the seed-row tap. Update
the SongRequestResultRow callback to call only that model method, and have the
model internally route to the correct behavior based on isLibraryAddMode while
preserving the existing button text and processing state.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 204b8031-8599-484c-9bcc-04cd7b801f45

📥 Commits

Reviewing files that changed from the base of the PR and between f77edb8 and 8695fcd.

📒 Files selected for processing (44)
  • PlayolaRadio/Views/Pages/AskQuestionPage/AskQuestionPageModel.swift
  • PlayolaRadio/Views/Pages/AskQuestionPage/AskQuestionPageView.swift
  • PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageModel.swift
  • PlayolaRadio/Views/Pages/BroadcastPage/BroadcastPageView.swift
  • PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageModel.swift
  • PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift
  • PlayolaRadio/Views/Pages/ContactPage/ContactPageModel.swift
  • PlayolaRadio/Views/Pages/ContactPage/ContactPageView.swift
  • PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageModel.swift
  • PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageView.swift
  • PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageModel.swift
  • PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageView.swift
  • PlayolaRadio/Views/Pages/FeedbackSheet/FeedbackSheetView.swift
  • PlayolaRadio/Views/Pages/HomePage/HomePageModel.swift
  • PlayolaRadio/Views/Pages/HomePage/HomePageView.swift
  • PlayolaRadio/Views/Pages/HomePage/UI Components/CircleSection.swift
  • PlayolaRadio/Views/Pages/HomePage/UI Components/HomePageStationList.swift
  • PlayolaRadio/Views/Pages/ListenerQuestionDetailPage/ListenerQuestionDetailPageModel.swift
  • PlayolaRadio/Views/Pages/ListenerQuestionDetailPage/ListenerQuestionDetailPageView.swift
  • PlayolaRadio/Views/Pages/MainContainer/MainContainer.swift
  • PlayolaRadio/Views/Pages/MainContainer/MainContainerModel.swift
  • PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageModel.swift
  • PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageView.swift
  • PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift
  • PlayolaRadio/Views/Pages/RecordPage/RecordPageView.swift
  • PlayolaRadio/Views/Pages/RewardsPage/PrizeTierRow.swift
  • PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift
  • PlayolaRadio/Views/Pages/RewardsPage/RewardsPageView.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRow.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCard.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCardModel.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPage.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPageModel.swift
  • PlayolaRadio/Views/Pages/SignInPage/SignInPageModel.swift
  • PlayolaRadio/Views/Pages/SignInPage/SignInPageView.swift
  • PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageModel.swift
  • PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift
  • PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageModel.swift
  • PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageView.swift
  • PlayolaRadio/Views/Pages/SupportPage/SupportPageModel.swift
  • PlayolaRadio/Views/Pages/SupportPage/SupportPageView.swift
  • PlayolaRadio/Views/Reusable Components/ListeningTimeTile/ListeningTimeTile.swift
  • PlayolaRadio/Views/Reusable Components/ListeningTimeTile/ListeningTimeTileModel.swift

Comment thread PlayolaRadio/Views/Pages/ContactPage/ContactPageModel.swift Outdated
Comment thread PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageModel.swift Outdated
Comment thread PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageModel.swift Outdated
Comment thread PlayolaRadio/Views/Pages/HomePage/UI Components/HomePageStationList.swift Outdated
Comment thread PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPageModel.swift Outdated
Comment thread PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageModel.swift Outdated
Comment thread PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift Outdated
Comment thread PlayolaRadio/Views/Pages/SupportPage/SupportPageModel.swift Outdated
- Pluralize '1 hour to go' (RewardsPageModel) and '1 vote' (StationSuggestionPageModel).
- Drop hardcoded default values from presentational subview params
  (HomePageStationList, HomeIntroSection, SongSearchResultRow,
  SongRequestResultRow, ListenerQuestionRow) so display text comes only from
  the model; previews/call sites pass explicit values.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift (1)

169-173: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move the fallback display selection into the page model.

ListenerQuestionRow still decides which user-facing text to render when transcription or listener name is missing. Pass display-ready strings from the model instead so this view stays presentation-only. As per coding guidelines, **/*View.swift: SwiftUI views should handle visuals only: layout, spacing, colors, and fonts; bind to model properties for all content; call model methods for all user actions; and avoid hardcoded strings or logic in the view.

🤖 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
`@PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift`
around lines 169 - 173, `ListenerQuestionRow` is still choosing fallback text
for missing transcription and listener name instead of receiving display-ready
content from the page model. Move that selection logic out of
`BroadcastersListenerQuestionPageView` into the corresponding model/view model,
then have the view bind directly to model-provided strings for transcription and
listener display text. Keep `ListenerQuestionRow` presentation-only by removing
the `question.transcription ?? missingTranscriptionText` and
`question.listener?.fullName ?? unknownListenerText` logic from the view.

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.

Outside diff comments:
In
`@PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift`:
- Around line 169-173: `ListenerQuestionRow` is still choosing fallback text for
missing transcription and listener name instead of receiving display-ready
content from the page model. Move that selection logic out of
`BroadcastersListenerQuestionPageView` into the corresponding model/view model,
then have the view bind directly to model-provided strings for transcription and
listener display text. Keep `ListenerQuestionRow` presentation-only by removing
the `question.transcription ?? missingTranscriptionText` and
`question.listener?.fullName ?? unknownListenerText` logic from the view.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 07630590-f414-41db-9fbe-add4ccfe805d

📥 Commits

Reviewing files that changed from the base of the PR and between 8695fcd and d3ee684.

📒 Files selected for processing (6)
  • PlayolaRadio/Views/Pages/BroadcastersListenerQuestionPage/BroadcastersListenerQuestionPageView.swift
  • PlayolaRadio/Views/Pages/HomePage/UI Components/CircleSection.swift
  • PlayolaRadio/Views/Pages/HomePage/UI Components/HomePageStationList.swift
  • PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift
  • PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageView.swift
  • PlayolaRadio/Views/Pages/StationSuggestionPage/StationSuggestionPageModel.swift

Move the string-extraction display-text computed properties/helpers in 11 page
models from the Properties area into a // MARK: - View Helpers section placed
after User Actions, per the project's model section ordering. Position/MARK only,
no logic or string changes.
@briankeane

Copy link
Copy Markdown
Contributor Author

@greptile review this

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift (1)

49-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid rendering a dangling First Aired label.

When airing.episode?.createdAt is missing, originallyAiredDateText returns "", so this property still emits First Aired .

Proposed fix
-  var originallyAiredText: String { "First Aired \(originallyAiredDateText)" }
+  var originallyAiredText: String {
+    guard !originallyAiredDateText.isEmpty else { return "" }
+    return "First Aired \(originallyAiredDateText)"
+  }
🤖 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 `@PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift` at
line 49, The originallyAiredText computed property in EpisodeRowModel should
avoid showing a bare “First Aired” label when originallyAiredDateText is empty.
Update the formatting logic so it only returns the full label when
airing.episode?.createdAt exists, and otherwise returns an empty string or omits
the label entirely. Keep the change localized to the EpisodeRowModel properties
that build originallyAiredText/originallyAiredDateText.
🤖 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.

Outside diff comments:
In `@PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift`:
- Line 49: The originallyAiredText computed property in EpisodeRowModel should
avoid showing a bare “First Aired” label when originallyAiredDateText is empty.
Update the formatting logic so it only returns the full label when
airing.episode?.createdAt exists, and otherwise returns an empty string or omits
the label entirely. Keep the change localized to the EpisodeRowModel properties
that build originallyAiredText/originallyAiredDateText.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2c123941-28a4-463e-bb20-e01e7dddccd5

📥 Commits

Reviewing files that changed from the base of the PR and between d3ee684 and 9d01bca.

📒 Files selected for processing (11)
  • PlayolaRadio/Views/Pages/ContactPage/ContactPageModel.swift
  • PlayolaRadio/Views/Pages/ConversationListPage/ConversationListPageModel.swift
  • PlayolaRadio/Views/Pages/EditProfilePage/EditProfilePageModel.swift
  • PlayolaRadio/Views/Pages/NotificationsSettingsPage/NotificationsSettingsPageModel.swift
  • PlayolaRadio/Views/Pages/RecordPage/RecordPageModel.swift
  • PlayolaRadio/Views/Pages/RewardsPage/RewardsPageModel.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/EpisodeRow/EpisodeRowModel.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesCard/SeriesCardModel.swift
  • PlayolaRadio/Views/Pages/SeriesListPage/SeriesListPageModel.swift
  • PlayolaRadio/Views/Pages/SongSearchPage/SongSearchPageModel.swift
  • PlayolaRadio/Views/Pages/SupportPage/SupportPageModel.swift

ListenerQuestionRow chose between showLessText/showMoreText with an in-view
ternary. Add expandToggleText(for:) on the model and pass the resolved string,
matching the RecordPage/AskQuestionPage pattern so the view holds no copy-
selection logic. The row keeps isExpanded for the chevron and line limit.
@briankeane

Copy link
Copy Markdown
Contributor Author

@greptile review this

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.

1 participant