Skip to content

[feature/ui] Preliminary UI design - #1

Merged
HoweiAY merged 12 commits into
mainfrom
feature/ui
Jul 24, 2026
Merged

HoweiAY merged 12 commits into
mainfrom
feature/ui

Conversation

@HoweiAY

@HoweiAY HoweiAY commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added a full alarm dashboard with countdowns, enable/disable controls, multi-select, bulk deletion, and editing.
    • Added alarm creation and editing with time, weekday, task, rounds, difficulty, sound, and snooze options.
    • Added interactive task experiences for Math, Memory, and Shake.
    • Added custom time wheels, sliders, radio buttons, and weekday controls.
  • Style
    • Introduced a consistent dark theme with Geist fonts and refreshed icons, colors, spacing, and navigation.
  • Documentation
    • Improved migration guide table formatting.

@HoweiAY HoweiAY changed the title [feature/ui] [feature/ui] Preliminary UI design Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 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: CHILL

Plan: Pro Plus

Run ID: d8ab1727-4877-46b3-9eae-fd8bbb60fb9e

📥 Commits

Reviewing files that changed from the base of the PR and between 94bd4f6 and 5bf68bc.

📒 Files selected for processing (5)
  • .husky/pre-commit
  • src/app/(main)/create-alarm/[alarmId].tsx
  • src/app/(main)/index.tsx
  • src/components/FormSlider.tsx
  • src/components/RadioButton.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • .husky/pre-commit
  • src/components/RadioButton.tsx
  • src/components/FormSlider.tsx
  • src/app/(main)/index.tsx

📝 Walkthrough

Walkthrough

The app adds a themed alarm-management interface with typed alarm data, creation and editing forms, interactive task controls, countdown utilities, updated task screens, dark Expo branding, and staged linting configuration.

Changes

Alarm foundation

Layer / File(s) Summary
Alarm data, time utilities, and theme
src/data/*, src/theme/*, src/utils/time.ts
Adds alarm types, mock alarms, form constants, shared theme tokens, and alarm countdown utilities.

Alarm creation and management

Layer / File(s) Summary
Alarm form and input controls
src/components/*, src/hooks/useCreateAlarmForm.ts, src/app/(main)/create-alarm/*
Adds creation/editing form state, time wheels, weekday buttons, sliders, radio buttons, sound and snooze controls, and create/edit routes.
Alarm list and editing interactions
src/app/(main)/index.tsx, src/components/AlarmCard.tsx, src/hooks/useAlarmById.ts
Replaces the landing screen with alarm cards, countdown state, enable toggles, selection mode, bulk actions, and edit navigation.

Task screens and application setup

Layer / File(s) Summary
Task screen theming and feedback
src/app/(alarm)/tasks/*
Applies theme tokens, headers, pressed states, input feedback, and shake progress UI to task screens.
Expo branding and developer hooks
app.json, package.json, .husky/*, src/app/_layout.tsx, docs/*
Configures dark branding, Geist fonts, status-bar styling, lint-staged hooks, and Markdown table formatting.

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

Sequence Diagram(s)

sequenceDiagram
  participant Home
  participant AlarmCard
  participant EditAlarmScreen
  participant CreateAlarmForm
  participant useCreateAlarmForm
  Home->>AlarmCard: render alarm actions
  AlarmCard->>Home: report selection or navigation press
  Home->>EditAlarmScreen: navigate with alarm id
  EditAlarmScreen->>CreateAlarmForm: render edit form
  CreateAlarmForm->>useCreateAlarmForm: update and confirm form
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the UI-focused changes, but "Preliminary UI design" is too vague to clearly describe the main update. Use a more specific title that names the primary change, such as the new alarm screens, shared theme, or alarm creation flow.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 feature/ui

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

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

🧹 Nitpick comments (6)
src/components/CreateAlarmForm.tsx (1)

85-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse getDaysString for richer feedback.

helperText shows the generic "Selected days" instead of listing which days are selected. getDaysString (already used in AlarmCard) would show the actual selection.

♻️ Suggested fix
-          <Text style={styles.helperText}>
-            {form.weekdaysSelected.length === 0 ? "Every day" : "Selected days"}
-          </Text>
+          <Text style={styles.helperText}>
+            {getDaysString(form.weekdaysSelected)}
+          </Text>
🤖 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 `@src/components/CreateAlarmForm.tsx` around lines 85 - 87, Update the
helperText rendering in CreateAlarmForm to reuse the existing getDaysString
helper, displaying the selected weekday names while preserving the “Every day”
output when weekdaysSelected is empty.
src/components/AlarmCard.tsx (1)

32-36: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Card is missing accessibility metadata.

The outer Pressable (primary interactive element for select/navigate) has no accessibilityRole or accessibilityLabel describing the alarm time/days/state, unlike WeekdayTextButton elsewhere in this PR.

♿ Suggested fix
     <Pressable
       style={({ pressed }) => [styles.card, pressed && styles.cardPressed]}
       onPress={() => onPress(alarm)}
       onLongPress={() => onLongPress(alarm)}
+      accessibilityRole="button"
+      accessibilityLabel={`Alarm at ${formatTime(alarm.hour, alarm.minute)}, ${getDaysString(alarm.days)}`}
     >
🤖 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 `@src/components/AlarmCard.tsx` around lines 32 - 36, The outer Pressable in
AlarmCard needs accessibility metadata. Add an appropriate accessibilityRole and
a descriptive accessibilityLabel that communicates the alarm time, scheduled
days, and enabled/disabled state, while preserving the existing press handlers.
src/components/TimeWheelPicker.tsx (1)

37-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wheel items lack accessibility labels.

Unlike WeekdayTextButton in this same PR, the per-row Pressable items here have no accessibilityRole/accessibilityLabel, so screen-reader users can't identify or select hour/minute/period values.

♿ Suggested fix
           <Pressable
             key={item}
             style={styles.item}
             onPress={() => handleItemPress(i)}
+            accessibilityRole="button"
+            accessibilityState={{ selected: i === index }}
+            accessibilityLabel={item}
           >
🤖 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 `@src/components/TimeWheelPicker.tsx` around lines 37 - 89, Update the per-item
Pressable in Wheel to include an appropriate accessibilityRole and an
accessibilityLabel derived from the rendered item value, so screen readers
identify each hour, minute, or period option and allow users to select it.
src/hooks/useCreateAlarmForm.ts (2)

214-238: 🎯 Functional Correctness | 🔵 Trivial

Confirm currently discards the draft alarm.

buildAlarm is referenced only via void buildAlarm; and never called before router.back(), so creating/editing an alarm doesn't persist anything yet. This matches the documented persistAlarm placeholder, so likely intentional for this UI-only pass — flagging to make sure it's tracked before this ships.

🤖 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 `@src/hooks/useCreateAlarmForm.ts` around lines 214 - 238, Implement the
persistAlarm flow in handleConfirm instead of discarding the draft: call
buildAlarm, persist the resulting alarm through the appropriate create or update
path, reschedule it, and navigate back only after persistence completes. Replace
the void buildAlarm placeholder while preserving the existing no-reset
navigation behavior.

78-93: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

alarmSoundSelected will show a raw file path instead of a display name.

fromAlarm sets alarmSoundSelected to alarm.sound when custom, but Alarm.sound only stores the file URI (per pickSound's own plan, alarmSoundUri gets the file:// path and alarmSoundSelected should get asset.name). Once sound picking is implemented, re-opening an alarm with a custom sound will render the raw URI as the label. Consider storing a separate display name field on Alarm (e.g. soundName) so round-tripping through edit doesn't lose the friendly name.

🤖 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 `@src/hooks/useCreateAlarmForm.ts` around lines 78 - 93, The custom sound label
in fromAlarm must not use Alarm.sound, since that field is the file URI. Add and
persist a separate display-name field on Alarm (such as soundName), set it when
pickSound selects an asset, and use it for alarmSoundSelected while retaining
alarm.sound for alarmSoundUri; preserve “Default” behavior for built-in sounds.
src/app/(main)/index.tsx (1)

111-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated select-all condition.

selectedIds.size === alarms.length && alarms.length > 0 is computed separately for the accessibilityLabel and the Text. Extracting a local allSelected variable avoids the duplication.

♻️ Suggested fix
+          {(() => {
+            const allSelected = selectedIds.size === alarms.length && alarms.length > 0;
+            return (
               <Pressable
                 accessibilityRole="button"
-                accessibilityLabel={
-                  selectedIds.size === alarms.length && alarms.length > 0
-                    ? "Unselect all"
-                    : "Select all"
-                }
+                accessibilityLabel={allSelected ? "Unselect all" : "Select all"}
                 onPress={selectAll}
               >
-                <Text style={styles.actionRowText}>
-                  {selectedIds.size === alarms.length && alarms.length > 0
-                    ? "Unselect all"
-                    : "Select all"}
-                </Text>
+                <Text style={styles.actionRowText}>
+                  {allSelected ? "Unselect all" : "Select all"}
+                </Text>
               </Pressable>
+            );
+          })()}
🤖 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 `@src/app/`(main)/index.tsx around lines 111 - 125, In the component containing
the select-all Pressable, extract the repeated `selectedIds.size ===
alarms.length && alarms.length > 0` expression into a local `allSelected`
variable, then reuse it for both `accessibilityLabel` and the action-row `Text`
while preserving the existing labels and behavior.
🤖 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 @.husky/pre-commit:
- Line 1: Update the pre-commit hook’s lint-staged invocation to use the locally
installed binary, such as ./node_modules/.bin/lint-staged or npx --no
lint-staged, preventing fallback to the package registry.

In `@src/app/`(main)/create-alarm/[alarmId].tsx:
- Around line 21-25: Update the render condition around the alarm loading view
to distinguish active loading from a completed lookup with no alarm. Keep the
loading indicator for loading states, and when loading is false and alarm is
null, render a not-found recovery state or navigate back to the appropriate
create flow.

In `@src/app/`(main)/index.tsx:
- Around line 98-174: Add flex: 1 to the alarm list’s wrapping View and, if
necessary, the FlatList in the screen component so the list participates in the
available-height flex chain and scrolls independently for long alarm
collections. Keep the existing list data, rendering, and styles unchanged.

In `@src/components/FormSlider.tsx`:
- Around line 35-50: Update the PanResponder callbacks in FormSlider’s
applyFraction flow to use event.nativeEvent.locationX rather than
gestureState.x0/moveX, and normalize the coordinate against usable instead of
trackWidth. Preserve the existing clamping, snapping, and disabled behavior
while ensuring values are calculated from the slider’s local coordinate system.

In `@src/components/RadioButton.tsx`:
- Around line 29-36: Update the selected-state rendering in RadioButton so the
selection dot remains visible when disabled; render the dot whenever selected is
true, while preserving the existing circleDisabled styling for disabled options.

---

Nitpick comments:
In `@src/app/`(main)/index.tsx:
- Around line 111-125: In the component containing the select-all Pressable,
extract the repeated `selectedIds.size === alarms.length && alarms.length > 0`
expression into a local `allSelected` variable, then reuse it for both
`accessibilityLabel` and the action-row `Text` while preserving the existing
labels and behavior.

In `@src/components/AlarmCard.tsx`:
- Around line 32-36: The outer Pressable in AlarmCard needs accessibility
metadata. Add an appropriate accessibilityRole and a descriptive
accessibilityLabel that communicates the alarm time, scheduled days, and
enabled/disabled state, while preserving the existing press handlers.

In `@src/components/CreateAlarmForm.tsx`:
- Around line 85-87: Update the helperText rendering in CreateAlarmForm to reuse
the existing getDaysString helper, displaying the selected weekday names while
preserving the “Every day” output when weekdaysSelected is empty.

In `@src/components/TimeWheelPicker.tsx`:
- Around line 37-89: Update the per-item Pressable in Wheel to include an
appropriate accessibilityRole and an accessibilityLabel derived from the
rendered item value, so screen readers identify each hour, minute, or period
option and allow users to select it.

In `@src/hooks/useCreateAlarmForm.ts`:
- Around line 214-238: Implement the persistAlarm flow in handleConfirm instead
of discarding the draft: call buildAlarm, persist the resulting alarm through
the appropriate create or update path, reschedule it, and navigate back only
after persistence completes. Replace the void buildAlarm placeholder while
preserving the existing no-reset navigation behavior.
- Around line 78-93: The custom sound label in fromAlarm must not use
Alarm.sound, since that field is the file URI. Add and persist a separate
display-name field on Alarm (such as soundName), set it when pickSound selects
an asset, and use it for alarmSoundSelected while retaining alarm.sound for
alarmSoundUri; preserve “Default” behavior for built-in sounds.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a7e08b55-071b-472a-a946-0a873a954680

📥 Commits

Reviewing files that changed from the base of the PR and between 3631f03 and 94bd4f6.

⛔ Files ignored due to path filters (5)
  • assets/fonts/Geist-Bold.ttf is excluded by !**/*.ttf
  • assets/fonts/Geist-Medium.ttf is excluded by !**/*.ttf
  • assets/fonts/Geist-Regular.ttf is excluded by !**/*.ttf
  • assets/fonts/Geist-SemiBold.ttf is excluded by !**/*.ttf
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (30)
  • .husky/pre-commit
  • app.json
  • docs/07-react-native-migration-guide.md
  • package.json
  • src/app/(alarm)/_layout.tsx
  • src/app/(alarm)/tasks/math-equation/[rounds]/[difficulty].tsx
  • src/app/(alarm)/tasks/memory-game/[rounds]/[difficulty].tsx
  • src/app/(alarm)/tasks/phone-shaking.tsx
  • src/app/(main)/_layout.tsx
  • src/app/(main)/create-alarm/[alarmId].tsx
  • src/app/(main)/create-alarm/index.tsx
  • src/app/(main)/index.tsx
  • src/app/_layout.tsx
  • src/components/AlarmCard.tsx
  • src/components/CreateAlarmForm.tsx
  • src/components/FormSlider.tsx
  • src/components/RadioButton.tsx
  • src/components/TimeWheelPicker.tsx
  • src/components/WeekdayTextButton.tsx
  • src/data/constants.ts
  • src/data/mockAlarms.ts
  • src/data/types.ts
  • src/hooks/useAlarmById.ts
  • src/hooks/useCreateAlarmForm.ts
  • src/theme/colors.ts
  • src/theme/index.ts
  • src/theme/radii.ts
  • src/theme/spacing.ts
  • src/theme/typography.ts
  • src/utils/time.ts

Comment thread .husky/pre-commit Outdated
Comment thread src/app/(main)/create-alarm/[alarmId].tsx Outdated
Comment thread src/app/(main)/index.tsx Outdated
Comment thread src/components/FormSlider.tsx Outdated
Comment thread src/components/RadioButton.tsx Outdated
HoweiAY added 5 commits July 24, 2026 19:16
npx can fall back to the registry when the binary is missing locally. Use
`npx --no` so the hook fails closed instead of fetching from the registry.
A resolved-but-null alarm previously kept the loading indicator on screen
indefinitely. Render a not-found recovery state with a link to the create
flow when loading is false and no alarm was found.
The list wrapper had no flex sizing, so a long alarm collection expanded to
content height and got clipped instead of scrolling. Add flex:1 to the
wrapper so the FlatList participates in the available-height flex chain.
gestureState.x0/moveX are screen-relative, so the selected value shifted
with page offset and could miss the minimum. Use the responder View's
local locationX and normalize against the usable track width.
A selected disabled option rendered without its dot, leaving its current
state unclear. Render the dot whenever selected, dimmed via dotDisabled
for disabled options.
@HoweiAY
HoweiAY merged commit 5ca1e67 into main Jul 24, 2026
1 check passed
@HoweiAY
HoweiAY deleted the feature/ui branch July 24, 2026 11:40
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