commits
- Change JETSTREAM_HOST/PORT to single JETSTREAM_URL with scheme
(wss:// implies port 443, ws:// implies port 80)
- Change CONSTELLATION_API to CONSTELLATION_URL (base URL only)
- Parse URL scheme to determine SSL vs plain WebSocket connection
- Update config.local.h.example with new URL format
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add JETSTREAM_HOST, JETSTREAM_PORT, and CONSTELLATION_API to config.h
with sensible defaults. Users can override these in config.local.h if
needed for custom deployments or testing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase polling frequency from 5s to 1s for faster state updates
- Play ripple animation on both connect and disconnect events
- Add corner LED animation (cycles 4 corners) when ESP32 connected but
no internet, providing visual feedback for connection state
- Fix Jetstream deletion events not updating calendar by maintaining
completion cache on create/delete events
- Add logging for ESP32 connection established, button presses, and
Jetstream events
- Use compact calendar state display (⊙/·) on startup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The board definition for ESP32-S3 QT Py assumes 8MB flash with TinyUF2
bootloader at offset 0x410000. For boards with only 4MB flash, this
fails because TinyUF2 is placed beyond the flash boundary.
This adds explicit 4MB overrides and disables TinyUF2 flashing,
allowing both 4MB and 8MB ESP32-S3 variants to work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Support both ESP32-S3 and ESP32-S2 QT Py boards. I2C pins are
auto-detected based on CONFIG_IDF_TARGET, so switching boards
only requires changing the `board =` line in platformio.ini.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Only show burst animation when ESP32 is connected to internet,
providing visual feedback that button presses will sync.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add dark mode CSS via prefers-color-scheme media query
- Disable save button until a different goal is selected
- Show loading spinner when save is pressed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds details for how to build this modification!
Adds OFFLINE_SYNC_MODE config option:
- 0: Block button presses when not synced
- 1: Calendar wins - local changes sync TO goals.garden (default)
- 2: goals.garden wins - remote overwrites local
First boot behavior (no saved goal):
- Request calendar's current LED state via new I2C protocol
- If LEDs blank: select first available goal
- If LEDs set: create "Everyday Calendar" goal and upload completions
New I2C commands for bidirectional state transfer:
- RSP_REQUEST_CAL_STATE: ESP32 requests calendar state
- CMD_CAL_STATE_PART1/2: Calendar sends its LED bitmap
Also adds ATProtoClient::createGoal() for creating new goals.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The query was still using goal.uri (nested) but the completion
records now use goalUri (flat field).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Store handle from ATProto session response and add getHandle()
- Display handle instead of DID in web UI (link still goes to DID profile)
- Change green highlight to use .saved class instead of :has(input:checked)
so the highlight stays on the saved goal, not the currently checked radio
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove hardcoded GOAL_URI from config
- Add web server at everydaycalendar.local for selecting goals
- Store selected goal in NVS flash (persists across reboots)
- Auto-select first goal on boot if none stored
- Background refresh of goals list every 5 minutes
- Goal switching: disconnects Jetstream, clears state, reconnects
Also updates completion record structure:
- Use simple goalUri field instead of goal strongref (uri+cid)
- Remove goalCid from codebase as it's no longer needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document hard-won debugging lessons:
- Timer2 ISR blocking causes LED flicker
- I2C bitmap protocol to minimize transactions
- ATmega328P 32-byte Wire buffer limit
- Jetstream-triggered sync instead of periodic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove verbose debug output:
- HTTP request URLs and response codes
- JSON parse error details
- Session refresh messages
- Individual item counts during sync
Keep useful logs:
- Startup and connection status
- Button press actions and results
- Sync completion summaries
- Error conditions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace individual SET_LED commands with bitmap protocol (2 parts)
to reduce I2C transactions from ~367 to 2, eliminating LED flicker
- Sync only on Jetstream (re)connect instead of every 60 seconds
- Save state to EEPROM after receiving from ESP32 for offline boot
- Add debug logging for Jetstream event processing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bar-raiser review fixes:
- Fix race condition: set g_calendarI2C before Wire.begin()
- Fix Jetstream delete events: look up completion by rkey in cache
- Increase sync limit from 50 to 400 responses (supports full year)
- Add findCompletionByRkey() for reverse rkey lookups
- Add bounds validation for month/day in Jetstream handler
Code cleanup:
- Remove verbose debug logging that caused LED flashing
- Remove dead code: EverydayCalendar_sync.cpp/h (unused serial implementation)
- Remove loop counter debug output from calendar sketch
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Serial communication failed due to Timer2 ISR blocking SoftwareSerial.
I2C is interrupt-driven and works reliably with LED multiplexing.
Changes:
- ESP32 acts as I2C slave at address 0x42 (calendar is master)
- Calendar polls ESP32 every 5 seconds for full state
- ESP32 maintains internal state, sends CLEAR + SET_LED commands
- Added "ready" flag so ESP32 doesn't respond before syncing with goals.garden
- Fixed connection timeout bug (millis() captured before poll completed)
- Added 200ms timeout to touch scanning to prevent hang when panel disconnected
- Removed old serial communication code (calendar_serial.cpp/h)
Hardware: Connect ESP32 STEMMA QT to calendar's J2 header (SDA/SCL)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change board from adafruit_qtpy_esp32s2 to generic esp32-s3-devkitc-1
- Add USB-CDC build flags for serial output over USB
- Configure 4MB flash size for QT Py ESP32-S3 variant
- Change flash mode from qio to dio for compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Brightness buttons (moon/sun) were falling through to the calendar
touch logic, causing LED toggles and burst animations at positions
(4,31) and (6,31). Now returns early after handling brightness
adjustment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add quick start guide for PlatformIO builds
- Document ESP32 goals.garden sync setup and configuration
- Document hardware wiring for WiFi sync
- Reorganize to highlight fork features
- Keep Arduino IDE instructions as alternative
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add WiFi co-processor firmware (QT Py ESP32-S2) that syncs the calendar
with goals.garden via ATProto:
- Bidirectional sync with goals.garden completions
- Real-time updates via Jetstream WebSocket
- PDS resolution via Microcosm slingshot API
- Efficient completion fetching via backlinks API
- NTP time sync with configurable timezone
- mDNS hostname support (everydaycalendar.local)
- Serial protocol for Arduino communication
Also adds PlatformIO configuration for both calendar and ESP32 builds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change JETSTREAM_HOST/PORT to single JETSTREAM_URL with scheme
(wss:// implies port 443, ws:// implies port 80)
- Change CONSTELLATION_API to CONSTELLATION_URL (base URL only)
- Parse URL scheme to determine SSL vs plain WebSocket connection
- Update config.local.h.example with new URL format
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase polling frequency from 5s to 1s for faster state updates
- Play ripple animation on both connect and disconnect events
- Add corner LED animation (cycles 4 corners) when ESP32 connected but
no internet, providing visual feedback for connection state
- Fix Jetstream deletion events not updating calendar by maintaining
completion cache on create/delete events
- Add logging for ESP32 connection established, button presses, and
Jetstream events
- Use compact calendar state display (⊙/·) on startup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The board definition for ESP32-S3 QT Py assumes 8MB flash with TinyUF2
bootloader at offset 0x410000. For boards with only 4MB flash, this
fails because TinyUF2 is placed beyond the flash boundary.
This adds explicit 4MB overrides and disables TinyUF2 flashing,
allowing both 4MB and 8MB ESP32-S3 variants to work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds OFFLINE_SYNC_MODE config option:
- 0: Block button presses when not synced
- 1: Calendar wins - local changes sync TO goals.garden (default)
- 2: goals.garden wins - remote overwrites local
First boot behavior (no saved goal):
- Request calendar's current LED state via new I2C protocol
- If LEDs blank: select first available goal
- If LEDs set: create "Everyday Calendar" goal and upload completions
New I2C commands for bidirectional state transfer:
- RSP_REQUEST_CAL_STATE: ESP32 requests calendar state
- CMD_CAL_STATE_PART1/2: Calendar sends its LED bitmap
Also adds ATProtoClient::createGoal() for creating new goals.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Store handle from ATProto session response and add getHandle()
- Display handle instead of DID in web UI (link still goes to DID profile)
- Change green highlight to use .saved class instead of :has(input:checked)
so the highlight stays on the saved goal, not the currently checked radio
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove hardcoded GOAL_URI from config
- Add web server at everydaycalendar.local for selecting goals
- Store selected goal in NVS flash (persists across reboots)
- Auto-select first goal on boot if none stored
- Background refresh of goals list every 5 minutes
- Goal switching: disconnects Jetstream, clears state, reconnects
Also updates completion record structure:
- Use simple goalUri field instead of goal strongref (uri+cid)
- Remove goalCid from codebase as it's no longer needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document hard-won debugging lessons:
- Timer2 ISR blocking causes LED flicker
- I2C bitmap protocol to minimize transactions
- ATmega328P 32-byte Wire buffer limit
- Jetstream-triggered sync instead of periodic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove verbose debug output:
- HTTP request URLs and response codes
- JSON parse error details
- Session refresh messages
- Individual item counts during sync
Keep useful logs:
- Startup and connection status
- Button press actions and results
- Sync completion summaries
- Error conditions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace individual SET_LED commands with bitmap protocol (2 parts)
to reduce I2C transactions from ~367 to 2, eliminating LED flicker
- Sync only on Jetstream (re)connect instead of every 60 seconds
- Save state to EEPROM after receiving from ESP32 for offline boot
- Add debug logging for Jetstream event processing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bar-raiser review fixes:
- Fix race condition: set g_calendarI2C before Wire.begin()
- Fix Jetstream delete events: look up completion by rkey in cache
- Increase sync limit from 50 to 400 responses (supports full year)
- Add findCompletionByRkey() for reverse rkey lookups
- Add bounds validation for month/day in Jetstream handler
Code cleanup:
- Remove verbose debug logging that caused LED flashing
- Remove dead code: EverydayCalendar_sync.cpp/h (unused serial implementation)
- Remove loop counter debug output from calendar sketch
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Serial communication failed due to Timer2 ISR blocking SoftwareSerial.
I2C is interrupt-driven and works reliably with LED multiplexing.
Changes:
- ESP32 acts as I2C slave at address 0x42 (calendar is master)
- Calendar polls ESP32 every 5 seconds for full state
- ESP32 maintains internal state, sends CLEAR + SET_LED commands
- Added "ready" flag so ESP32 doesn't respond before syncing with goals.garden
- Fixed connection timeout bug (millis() captured before poll completed)
- Added 200ms timeout to touch scanning to prevent hang when panel disconnected
- Removed old serial communication code (calendar_serial.cpp/h)
Hardware: Connect ESP32 STEMMA QT to calendar's J2 header (SDA/SCL)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change board from adafruit_qtpy_esp32s2 to generic esp32-s3-devkitc-1
- Add USB-CDC build flags for serial output over USB
- Configure 4MB flash size for QT Py ESP32-S3 variant
- Change flash mode from qio to dio for compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Brightness buttons (moon/sun) were falling through to the calendar
touch logic, causing LED toggles and burst animations at positions
(4,31) and (6,31). Now returns early after handling brightness
adjustment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add quick start guide for PlatformIO builds
- Document ESP32 goals.garden sync setup and configuration
- Document hardware wiring for WiFi sync
- Reorganize to highlight fork features
- Keep Arduino IDE instructions as alternative
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add WiFi co-processor firmware (QT Py ESP32-S2) that syncs the calendar
with goals.garden via ATProto:
- Bidirectional sync with goals.garden completions
- Real-time updates via Jetstream WebSocket
- PDS resolution via Microcosm slingshot API
- Efficient completion fetching via backlinks API
- NTP time sync with configurable timezone
- mDNS hostname support (everydaycalendar.local)
- Serial protocol for Arduino communication
Also adds PlatformIO configuration for both calendar and ESP32 builds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>