feat: Android release fix, Orbit time picker, calendar picker + pagination, retrospective fix - #306
Conversation
…calendar picker + pagination - fix(mobile): pin @siteed/audio-studio to 3.2.0 (3.2.1 made reject(code) nullable, breaking the release AAB Kotlin compile against expo-modules-core) - feat(web): rebuild the time selector as a custom Orbit popover (no browser picker), trigger height matched to the date field; both pickers derive 12h/24h from profile.uses24HourClock - feat(calendar): client-side pagination of the events list + a calendar picker section (web + mobile) backed by GET /calendar/calendars + PUT /selected-calendars; events show their source calendar name - fix(retro): render one-time tasks as Completed / Not completed instead of a percentage - shared: calendarId/calendarName on events, calendar endpoints + UserCalendar types, uses24HourClock on the profile schema, isOneTime on retrospective stats Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Paired with thomasluizon/orbit-api#249 (the backend half: multi-calendar sync, retrospective caps, server-derived clock format). |
|
There was a problem hiding this comment.
Code Review: PR #306 — APPROVE
Summary
PR bundles four features: Android release fix (pin audio-studio to 3.2.0), custom Orbit time picker, Calendar picker + pagination, and retrospective one-time task display fix. All land on both platforms, i18n is in sync, schema changes are additive, and tests cover all new paths.
Findings
Critical / High
None
Medium
calendarId, calendarName, and uses24HourClock declared .optional() — may break if API emits null
- location:
packages/shared/src/types/calendar.ts:37-38,packages/shared/src/types/profile.ts:24 - issue: Zod's
.optional()acceptsundefined(absent key) but throws onnull. The default C#System.Text.Jsonserialiser emitsnullfor nullable strings/bools rather than omitting the key, unlessWhenWritingNullis configured. Every other nullable string in the same schema uses.nullable()(e.g.startDate,endTime,recurrenceRule). IfcalendarId,calendarName, oruses24HourClockfollow the same C# null convention,userCalendarsSchema.parse()and the profile parse will throw at runtime. - risk: Calendar picker section and time picker 12h/24h detection silently broken on real API responses.
- fix: Apply
.nullable().optional()to match the existing schema convention, or confirm the C# DTO side usesWhenWritingNull:
// packages/shared/src/types/calendar.ts
calendarId: z.string().nullable().optional(),
calendarName: z.string().nullable().optional(),
// packages/shared/src/types/profile.ts
uses24HourClock: z.boolean().nullable().optional(),- reference: CLAUDE.md rule 8; sibling fields
startDate,endTime,recurrenceRuleall use.nullable()
Subagents
| Agent | Verdict |
|---|---|
| parity-checker | PAIRED (web actions/calendar.ts has no mobile mirror — correct adapter-split) |
| i18n-syncer | IN SYNC (14 new keys in both locales; total 1,986 keys each) |
| contract-aligner | NOT VERIFIABLE IN CI (orbit-api is Windows-local; 1 risk flag = Medium above) |
| security-reviewer | N/A (no orbit-api changes) |
Validation
| Check | Result |
|---|---|
| Lint | PASS (static) |
| Type check | PASS (static) |
| Tests | N/A (node_modules not installed; author reports 1013/1619/620 green) |
| Build (api) | N/A |
What is good
- Android pin is surgical, root cause in PR body.
from12Houredge cases correct (12-noon, midnight).- Optimistic update + rollback pattern correct in both
useSetSelectedCalendarshooks. - 14 new i18n keys in both locales, correct interpolation.
- Zero dead code /
any/console.logacross 31 files. - All schema additions are optional and additive — no backward-compat breaks.
Recommendation
Resolve the Medium before or shortly after merge by either confirming the C# serialiser omits null for those three fields, or applying .nullable().optional(). 2-minute fix. Everything else is clean.
…ity) (#313) #306 rebuilt the web time selector as a custom Orbit popover but left the mobile picker on the native Android clock dial — mobile only got the 12h/24h profile wiring (8 lines), not the UI. Port the web component to mobile: scrollable hour/minute/AM-PM columns with the same to12Hour/from12Hour/parseTime logic, violet selection pill, and the "Selecionar horário"/"Concluir" header. is24Hour drives HOURS_24 (no period column) vs HOURS_12 + PERIODS. Remove the now-unused @react-native-community/datetimepicker (component import, package.json, app.json plugin, vitest alias, test mock, lockfile) and rewrite the mobile test against the popover behavior. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



The orbit-ui-mobile half of a paired, cross-repo session change (sibling: the orbit-api PR linked in the comments). Everything from this session in one PR.
Android release build fix
@siteed/audio-studiopinned to 3.2.0. Root cause: 3.2.1 (published 2026-06-20) changed itsrejectoverride'scodeparam to nullable, which no longer matchesexpo-modules-core'sPromiseinterface → the release AAB Kotlin compile failed. 3.2.0 is the last good version; the^caret had silently pulled 3.2.1.Time selector → custom Orbit component
The habit form's time field was a raw
<input type="time">(the ugly browser popup). Rebuilt as a custom Orbit popover (hour / minute / AM-PM columns, violet-selected, Roboto tabular numerals), with the trigger matched to the date field's exact height. Mobile keeps the native Android dialog behind a matching trigger. Both derive 12h/24h fromprofile.uses24HourClock(computed server-side from the user's timezone — São Paulo→24h, New York→12h), read from the already-cached profile (no extra fetch).Calendar picker + pagination
Retrospective display
One-time tasks render Completed / Not completed instead of a percentage (paired with the backend cap +
isOneTime).Tests
shared 1013, web 1619, mobile 620 — all green. The only failures are the pre-existing local
@sentry/*+@playwright/testinstall gap, which CI resolves vianpm ci.🤖 Generated with Claude Code