Skip to content

expo-kloq: native Expo port of kloq (v2) - #231

Merged
supalarry merged 16 commits into
mainfrom
expo-kloq-v2
Nov 5, 2026
Merged

supalarry merged 16 commits into
mainfrom
expo-kloq-v2

Conversation

@PeerRich

@PeerRich PeerRich commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Supersedes #229.

packages/expo-kloq is a new React Native port of kloq. It renders a calendar with native controls: SwiftUI on iOS through @expo/ui, with Liquid Glass on iOS 26+ through expo-glass-effect, and Jetpack Compose on Android. apps/expo-kloq-example is a runnable development build of it that works on iOS 27.

Architecture

  • kloq's model is imported, never copied. src/model.ts is the only file that imports kloq. It pulls in kloq's store (InternalStore), the calendar-source registry, projection, the month, year and agenda layouts, overlap, recurrence expansion, editOccurrence/deleteOccurrence/applyPlan, RRULE presets, the quick-add parser, reminders, time zones and the timeline window. kloq's root entry imports its DOM components, so kloq gets one new export, "./lib/*". Nothing in packages/kloq/src changed.
  • use-kloq.ts is the controller. It holds the store, the registered calendars, the current view and anchor date, and the save and delete paths. When a repeating event is saved or deleted, it asks an injected "scope asker" which occurrences to change, then applies kloq's recurrence plan.
  • Views (views/) are plain React Native views drawn over kloq's layout:
    • TimeGrid covers day, 3 days and week. It has an all-day lane, a now line and a gutter that shows several time zones. It opens scrolled to 8 AM, has 7 equal columns, and shows a single truncated line for narrow overlapping blocks.
    • MonthView has 6 rows of 7 equal columns, with one-line chips and "+N more".
    • AgendaView (shown as "Schedule") groups events by day and shows the title and time on each row.
    • YearView shows 12 mini-months, shaded by how busy each day is. Every day can be tapped, and the view opens on the current month.
    • TimelineView has one lane per visible calendar, with the day's hours running across.
  • Native controls live in src/native/*.ios.tsx and *.android.tsx. Both platforms implement the same props, defined in native/types.ts.
Where iOS (SwiftUI via @expo/ui) Android (Compose via @expo/ui)
Toolbar Glass Buttons (previous, Today, next) in a GlassEffectContainer, plus a Menu holding Quick add, a View Picker (3 days, Timeline and the rest), "Calendars & time zones" and Reset OutlinedButtons + DropdownMenu
View switcher Segmented Picker SingleChoiceSegmentedButtonRow
Floating bar expo-glass-effect GlassContainer + GlassView (New event, Quick add) same (a plain fill where Liquid Glass isn't available)
Event sheet UIKit page sheet (Modal with pageSheet) whose actions stay pinned above a SwiftUI Form: TextFields, an all-day Toggle, compact DatePickers (calendar and wheel popovers), menu Pickers for Repeat, Calendar and Alerts, and a custom-repeat builder (segmented frequency, Stepper, weekday button toggles, end date or count) TextField, Switch, DatePickerDialog/TimePickerDialog, dropdowns
Scope dialog ConfirmationDialog, shown as a popover pointing at Save (this event / this and following / all; only "this" and "all" on the first occurrence) AlertDialog
Quick add SwiftUI TextField with a live preview of what kloq's parser understood TextField
Calendars & zones SwiftUI Form of Toggles (calendar visibility, extra gutter zones, 24-hour clock) Switch rows

Fixes for the bugs found in #229

  • iOS 27 launch. iOS 27 requires the scene life cycle, and the SDK 57 prebuild template doesn't use it. The config plugin plugins/with-scene-delegate.js makes the AppDelegate an ExpoReactNativeFactoryProvider and points UIApplicationSceneManifest at Expo's own ExpoAppSceneDelegate. No hand edits to ios/.
  • Sizing. Every @expo/ui view sits inside a sized Host. The event sheet's Form fills the sheet and scrolls, and Save stays in the pinned header.
  • Layout. The toolbar respects the safe area. The segmented switcher sits in its own row below the toolbar. The scope dialog's hidden trigger is a 1 pt host that takes no space in the layout.
  • Views. Agenda rows show title and time, not the title twice. The year grid's days can be tapped. Quick add renders its input and creates events.
  • Example data. The example registers four calendars across two accounts (one of them read-only) and seeds events relative to today, using kloq's sample week plus a few weeks of extras.
  • One React. The example's Metro config pins React, React Native and the native modules' JS to the app. Without that, kloq's sources would load the web apps' React 19.3 next to React Native's 19.2.

How to run

bun install
bun run --cwd apps/expo-kloq-example prebuild -- -p ios   # LANG=en_US.UTF-8 if pod install complains
bun run --cwd apps/expo-kloq-example ios                  # dev build + Metro

The example needs a development build; Expo Go won't work because @expo/ui and expo-glass-effect are native modules. maestro/demo.yaml walks through every interaction and maestro/screenshots.yaml captures each view. Run both against a Release build (bunx expo run:ios --configuration Release), because a debug build's dev-client gear button covers the sheet's Save button.

Verification

  • Ran on the iPhone 18 Pro simulator (iOS 27.2) as a Release build. Maestro drove the full demo flow with no failing steps: every view, navigation, the time-wheel and calendar pickers, all-day, the calendar picker, the Repeat menu and custom builder, editing one occurrence through the scope dialog, deleting "this and following", adding an alert, a new event, quick add, the calendar and time-zone toggles, and the multi-zone gutter.
  • biome ci . passes, and tsc passes for expo-kloq, the example and kloq.
  • A demo video and one screenshot per view and sheet are attached separately.

Decisions and known gaps

  • License. expo-kloq is MIT with its own LICENSE, like kloq. AGENTS.md now lists it.
  • Android. The Compose implementations type-check but have not been run on an emulator.
  • Interactions. There is no drag, resize or long-press-to-move yet; kloq's drag engine is DOM-based. Events are created by tapping an empty slot, with New event, or with Quick add.
  • Gutter captions. The extra gutter columns are labelled with city names. Hermes' short zone names lost the offset ("GMT+9" came out as "GMT").
  • Toggle labels. Tapping a calendar or zone toggle's label doesn't flip it; only the switch does. This is standard SwiftUI behaviour.
  • Dialog cancel. On iOS 26+ the scope dialog shows as a popover, which has no Cancel button. Tapping outside it cancels.
  • No unit tests. The model is kloq's own and is tested upstream. This package is checked through the Maestro flows.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Rocky and others added 5 commits November 1, 2026 20:51
expo-kloq reuses kloq's store, projection, layout, recurrence, RRULE, quick
add, reminder and time zone modules; the root entry pulls in the DOM
components, so the model needs a path of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G
<KloqCalendar> runs on kloq's own model (imported through src/model.ts, never
copied): day, 3-day and week time grids with a multi-zone gutter, month,
year, schedule and timeline views; a native page sheet editor (SwiftUI Form:
compact date/time pickers, all-day toggle, calendar and repeat menus, a
custom RRULE builder, alerts) with the this / following / all dialog; quick
add on kloq's parser; calendar and zone settings. iOS uses @expo/ui's SwiftUI
components and Liquid Glass (expo-glass-effect); Android implements the same
props with Jetpack Compose. MIT, like kloq.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G
Works on iOS 27: plugins/with-scene-delegate.js adopts the scene life cycle
(Expo's ExpoAppSceneDelegate) that SDK 57's prebuild template lacks. Metro
pins React and the native modules' JS to the app so kloq's sources share
one React. Seeds are relative to today. maestro/demo.yaml walks every
interaction; maestro/screenshots.yaml captures each view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G
…d; Bun 1.4.2 lockfile

The lockfile had been written by an older Bun (lockfile v1). Pinning the
versions the repository already resolves keeps the change additive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxeHwHtptoRw4q5YBnkz1G
@PeerRich
PeerRich marked this pull request as ready for review November 1, 2026 21:22
@cosscom cosscom deleted a comment from CLAassistant Nov 1, 2026
supalarry and others added 11 commits November 5, 2026 10:36
Picking one and saving locked the event: it could not be edited, deleted
or moved back. Offer the writable calendars plus the event's own, like
kloq's calendarChoices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tap ignored the lane, so every event landed on the default calendar,
even from the read-only Holidays lane. Pass the lane's calendar through
onCreate into the draft, and don't create on read-only lanes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changing an alert's time dropped its method, and neither form showed it.
Keep the method when replacing, and label alerts with kloq's
describeReminder, which also names offsets outside the presets properly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"(No title)" was saved as the title and came back in the field on reopen.
Store "" like kloq, and show kloq's "Untitled" in the views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Timeline steps a day at a time, so "Next timeline" said the wrong thing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hour12 defaulted to true, so a 24-hour locale saw "1 PM" in the grid next
to "13:00" in the native pickers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"S M T W T F S" read the same for Tuesday and Thursday, and for Saturday
and Sunday. The example's Maestro flows now tap the full name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same label as kloq's event block, including the continuation notes on a
cross-midnight event. The example's Maestro flows match the new label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The date number becomes a button labelled with the date and event count.
The cell itself stays accessible={false}: an accessible container would
hide the event chips inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview dropped the year on a far date and the next-day note on an
overnight event. quickAddPreview gives kloq's own line. The parser's
all-day end is inclusive, so add a day like kloq, which matters once the
parser returns multi-day ranges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Nov 5, 2026 •

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 all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ supalarry
❌ Rocky


Rocky seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@supalarry

Copy link
Copy Markdown
Contributor

Artifact showing before and after of issues that last 10 commits fixed https://claude.ai/artifact/UjikbYU6PkrH64TF3V6EwD#d29f3487-713e

@supalarry
supalarry self-requested a review November 5, 2026 12:35
@supalarry
supalarry added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 2b957fe Nov 5, 2026
14 of 15 checks passed
@supalarry
supalarry deleted the expo-kloq-v2 branch November 5, 2026 12:53
@linear-code

linear-code Bot commented Nov 5, 2026

Copy link
Copy Markdown

ENG-8111

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.

3 participants