Skip to content

feat(calendar): year arrows + week/N-day time-grids + stats redesign + sync polish (#8, #13) - #326

Merged
thomasluizon merged 2 commits into
mainfrom
feat/calendar-views
Jun 27, 2026
Merged

feat(calendar): year arrows + week/N-day time-grids + stats redesign + sync polish (#8, #13)#326
thomasluizon merged 2 commits into
mainfrom
feat/calendar-views

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Calendar enhancements (web + mobile)

Both platforms

Web/desktop only (by design — a multi-column hourly grid doesn't fit a phone)

  • View switcher Month / Week / Range + a Google-Calendar-style CalendarTimeGrid (time gutter + day columns, hourly rows, an all-day band for untimed habits, timed habits as dueTime blocks with overlap lanes, a faint violet now-line). Powers Week (7 cols) and custom contiguous Range (pick a span in the mini-calendar → that many columns). Mobile keeps month.

Validation

type-check clean · lint 0 errors · tests web 1668 · mobile 640 · shared 1031 (i18n parity) — all green. Semantic tokens only, reduced-motion-gated.

🤖 Generated with Claude Code

…olish (#8, #13)

Year-skip arrows on month nav (both platforms). Month-stats redesigned as a data-driven extensible StatTile grid. Web: view switcher (Month/Week/Range) + Google-Calendar-style time-grid (hourly rows, day columns, timed-habit blocks, all-day band, now-line) for week + custom contiguous-range; mobile keeps month. Sync screen: Sincronizar agora->Sincronizar, deselect-all text->icon button. web+mobile parity for shared parts; grids web-only by design; both locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
orbit-ui-mobile-web Ignored Ignored Jun 27, 2026 5:28pm

Request Review

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

Code Review: PR #326

Scope: PR #326 — feat(calendar): year arrows + week/N-day time-grids + stats redesign + sync polish (#8, #13)
Recommendation: APPROVE

Summary

PR #326 delivers four calendar enhancements across both platforms: year-skip arrows on the month-nav header, a data-driven StatTile stats grid replacing the flat settings-row list, sync-screen polish (label shortening + icon button for deselect-all), and — web-only by design — Month / Week / Range view switcher backed by a CalendarTimeGrid Google-Calendar-style hourly grid. The cross-platform parity is correct, i18n keys are fully in sync, and no shared contract types are changed. All adversarial passes came back refuted. One Medium finding stands: a stray paywall research file was accidentally committed into this calendar PR.

Findings

Critical

None.

High

None.

Medium

[MEDIUM] Stray .claude/research/paywall-redesign.md committed in wrong PR

  • dimension: Dead/stale code (#2)
  • location: .claude/research/paywall-redesign.md (new file, 46 lines)
  • issue: This file is a design brief for the paywall/upgrade redesign (PR #6, task #7). It has no relationship to the calendar enhancements in this PR. .claude/research/ is tracked by git (not listed in .gitignore), so it ships with the merge.
  • risk: The paywall research document is permanently attached to this calendar feature commit, polluting the git log with an unrelated artifact.
  • fix: Remove .claude/research/paywall-redesign.md from this branch before merging. Re-commit it on the paywall feature branch (PR #6/#7) where it belongs.
  • reference: CLAUDE.md rule 2 — "Delete unused code immediately."

Low / Info

None.

Subagents

Agent Verdict
parity-checker PAIRED
i18n-syncer IN SYNC
contract-aligner N/A — no packages/shared/src/types/* or orbit-api changes
security-reviewer N/A — no orbit-api changes

Parity: Web-only features (CalendarTimeGrid, CalendarWeekView, CalendarRangeView, CalendarWeekNav) are correctly absent from mobile — PR body documents this as a design decision. All shared features (year arrows, StatTile stats grid, SelectAllToggle icon button, CalendarStats) are mirrored on both platforms.

i18n: All 12 new keys (common.previousWeek/nextWeek, calendar.goToCurrentWeek, calendar.view.*, calendar.timeGrid.*) are present in both en.json and pt-BR.json. The syncNow shortening is consistent in both locales.

Adversarial passes (all three refuted):

  1. CalendarTimeGrid 199 lines over hard cap — REFUTED. TimedBlock and AllDayChip already extracted; function body is 15 lines of logic + pure JSX composition for three tightly-coupled zones sharing perColumn/gridTemplate. Splitting would only add prop-drilling.
  2. Mobile SelectAllToggle missing accessibilityState — REFUTED. The self-describing label pattern ("Select all" / "Deselect all") is the intentional a11y signal confirmed by the test suite. accessibilityState={{ selected }} maps to aria-selected — wrong semantics for a toggle button.
  3. Now-line stale after mount — REFUTED. CalendarTimeGrid unmounts/remounts on every view-switch (React conditional rendering), so nowMinutes is always fresh at mount. Habit-tracker usage pattern makes the stale-for-hours scenario implausible.

Validation

Check Result
Lint PASS (per PR body: 0 errors)
Type check PASS (per PR body: clean)
Tests PASS (per PR body: web 1668 · mobile 640 · shared 1031 — all green)
Build (api) N/A

Deferred — N/A dimensions

  • Comment policy (#4): N/A — no banned // narrative comments in any changed file.
  • No-workaround (#5): N/A — useCalendarRange correctly reuses existing fetchCalendarMonth endpoint; no client-side reshaping.
  • Type safety (#6): N/A — no any, as any, or escape hatches in the diff.
  • console.log (#7): N/A — no console.log in production code.
  • Contract drift + backward-compat (#11): N/A — packages/shared/src/types/* and orbit-api DTOs untouched.
  • Security (#12): N/A — no new auth boundaries or endpoints.
  • Backend hard rules (#13): N/A — orbit-api not touched.
  • DESIGN.md / AI-slop (#8): Checked and clean. All new components use semantic tokens only (var(--primary), var(--fg-*), var(--hairline), var(--bg-card)). color-mix(in srgb, var(--primary) NN%, transparent) is the correct primary-tint idiom. No transition-all, no h-screen, no raw --slate-* or hardcoded violet rgba. Scene-sentence: "A navy-cosmic weekly time grid with a violet now-line and status-colored habit blocks in Google Calendar columns" — Orbit-specific, not generic SaaS.

What's good

  • StatTile grid replacing three SettingsRow stubs: clean array-driven pattern, adding a stat is one object literal.
  • useCalendarRangeQuery private helper: eliminates duplication between useCalendarData and useCalendarRange without over-abstracting — both callers have clean, separately-exported APIs.
  • CalendarGrid range-highlight (isInRange, isEndpoint, highlighted): correctly layered on top of selected without touching hit-targets or status-dot logic.
  • layoutTimed cluster-flush algorithm: compact (44 lines), correct, handles overlap lanes and edge cases cleanly.
  • All tests assert behavior, not implementation details — exactly what the testing standard requires.
  • Week-view intentionally absent from mobile — right call, clearly documented.

Recommendation

Remove .claude/research/paywall-redesign.md from this branch, then merge. All feature work is correct, cross-platform parity is complete, i18n is in sync, and no breaking contract changes exist.

🤖 Generated with Claude Code

# Conflicts:
#	apps/mobile/test-mocks/lucide-react-native.ts
#	apps/web/app/(app)/calendar/page.tsx
@thomasluizon
thomasluizon merged commit 9ed0409 into main Jun 27, 2026
8 checks passed
@thomasluizon
thomasluizon deleted the feat/calendar-views branch June 27, 2026 17:29
@sonarqubecloud

Copy link
Copy Markdown

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

Code Review: PR #326

Scope: PR #326 in thomasluizon/orbit-ui-mobile — feat(calendar): year arrows + week/N-day time-grids + stats redesign + sync polish (#8, #13)
Recommendation: APPROVE

Summary

PR #326 adds four coordinated calendar enhancements across web and mobile: year-skip arrows on the month-nav header, a data-driven StatTile-based month-stats section replacing the flat SettingsRow list, a SelectAllToggle icon-button replacing the overflowing text button on the sync screen, and (web-only by deliberate design) a Google-Calendar-style time grid powering new Week and Range views. The diff is well-factored, i18n-parity is clean in both locales, cross-platform parity is correct for all shared features, and no shared types or API contracts were touched. One Medium finding exists (stale "now" line in the time grid), but nothing blocks merge.

Findings

Critical

None

High

None

Medium

[MEDIUM] nowMinutes frozen at mount — now-line does not track real time

  • dimension: Correctness (#1)
  • location: apps/web/components/calendar/calendar-time-grid.tsx:205-208
  • issue: nowMinutes is computed once via useMemo(() => ..., []) with an empty dependency array. The calculated minute offset captures the clock at component mount and never updates. On a week/range view left open across a clock boundary (e.g. hour rollover, or simply left open for 30+ minutes), the violet "now" line will be visually wrong.
  • risk: The now-line drifts silently; no crash, no data loss, but users looking at the week grid an hour later will see the now-line pointing to the wrong hour.
  • fix: Replace useMemo with useState + a setInterval that refreshes every minute:
    const [nowMinutes, setNowMinutes] = useState(() => {
      const now = new Date()
      return getHours(now) * 60 + getMinutes(now)
    })
    useEffect(() => {
      const id = setInterval(() => {
        const now = new Date()
        setNowMinutes(getHours(now) * 60 + getMinutes(now))
      }, 60_000)
      return () => clearInterval(id)
    }, [])
  • reference: CLAUDE.md rule 1 (Correctness / boundary conditions)

Low / Info

None (per signal gate — no nits posted)

Subagents

Agent Verdict
parity-checker PAIRED — year arrows, CalendarStats, and SelectAllToggle all mirrored; week/range/time-grid are web-only per documented design decision in PR body
i18n-syncer IN SYNC — all 10 new keys present in both en.json and pt-BR.json; syncNow copy change applied to both locales
contract-aligner N/A — no packages/shared/src/types/* or endpoints.ts changes; no orbit-api changes
security-reviewer N/A — no orbit-api changes in this PR

Validation

Check Result
Lint PASS (PR body: "lint 0 errors")
Type check PASS (PR body: "type-check clean")
Tests PASS (PR body: "web 1668 · mobile 640 · shared 1031 — all green")
Build (api) N/A — no orbit-api changes

Deferred — N/A dimensions & files not verdicted

  • Dimension 13 (Backend hard rules) — N/A: no orbit-api files in this diff.
  • Dimension 11 (Contract drift / backward-compat guard) — N/A: no packages/shared/src/types/* or Zod schema lines changed; no field additions/removals to check.
  • Dimension 12 (Security — API side) — N/A: no orbit-api files changed. Frontend security checks (no dangerouslySetInnerHTML, no raw user content in JSX, no auth state leakage) all pass.
  • .claude/research/paywall-redesign.md — Internal design brief committed to .claude/research/ (not shipped code); no behavior reviewed.

What's good

  • Clean DRY refactor: useCalendarRangeQuery private function extracted from useCalendarData so useCalendarData and useCalendarRange share a single query setup — exactly the right level per rule 10.
  • Intentional platform split documented: The PR body explicitly names the reason week/range views are web-only ("a multi-column hourly grid doesn't fit a phone"), satisfying the parity exception for platform adapters.
  • Test coverage: New components (CalendarStats, SelectAllToggle, CalendarTimeGrid, CalendarWeekNav) all have focused behavior tests. CalendarTimeGrid tests use data-testid + data-hour attributes rather than class names — correct per testing conventions.
  • Accessibility: Both SelectAllToggle implementations expose the toggled label as the accessible name (web: aria-label + aria-pressed; mobile: accessibilityLabel + accessibilityRole). Year-skip buttons carry descriptive labels. All icon-only controls have text alternatives.
  • Semantic tokens only: color-mix(in srgb, var(--primary) …) for range highlighting, var(--fg-*), var(--hairline), var(--bg-card) throughout — no raw slate values or hardcoded rgba.
  • layoutTimed lane algorithm: Correctly clusters overlapping timed events and assigns non-overlapping lanes — handles the edge case of entries starting at the exact end of a prior block (strict >= boundary).

Recommendation

One Medium finding (stale now-line) — does not block merge, but should be tracked as a follow-up. A setInterval-based refresh (60 s) fixes it in ~10 lines. Everything else is clean. APPROVE.

thomasluizon added a commit that referenced this pull request Jun 27, 2026
…339)

#326/#333 shipped the calendar Week (7-day) and Interval (N-day) time-grid
views to apps/web only. Mirror them to apps/mobile for cross-platform parity.

Mobile:
- Mês / Semana / Intervalo view switch on the calendar screen.
- Week view: 7-day time grid with week-granularity nav + show-recurring toggle.
- Interval view: range-picker mini-calendar (14-day cap + clamped notice) plus
  an N-day time grid + show-recurring toggle.
- New CalendarTimeGrid, CalendarWeekView, CalendarRangeView, ShowRecurringToggle
  and CalendarWeekNav; CalendarGrid gains an optional range-highlight mode;
  useCalendarRange hook.

Shared:
- Lift clampRangeToMaxDays + MAX_RANGE_DAYS from apps/web/lib/calendar-range to
  packages/shared/src/utils/calendar-range.ts so both platforms consume one
  copy (web re-points its import; no duplication). filterRecurringEntries was
  already shared and is reused.

Web:
- Make the shared CalendarTimeGrid horizontally scrollable: day columns keep an
  ~80px minimum width and scroll left/right with the left time gutter and the
  header/all-day rows pinned, instead of compressing at narrow widths. Applies
  to both the web week and interval views; mobile mirrors the same behavior.

Tests: shared 14-day clamp; mobile time-grid render (timed + all-day, N
columns, header tap), view switch, and show-recurring filtering.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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