aboutsummaryrefslogtreecommitdiff
path: root/modules/calendar-sync.el
Commit message (Collapse)AuthorAgeFilesLines
* refactor(load-graph): route C-; registration through the keymap APICraig Jennings12 days1-1/+1
| | | | | | Migrated all 31 cj/custom-keymap registration sites across 24 modules from direct (keymap-set cj/custom-keymap ...) calls to cj/register-prefix-map and cj/register-command. Consumers no longer reference cj/custom-keymap directly, so keybindings.el is the sole owner of the C-; prefix and modules reach it only through the API (each already requires keybindings from Phase 2). Behavior-preserving: I dumped every C-; binding before and after the migration and they're identical: 279 bindings, each resolving to the same command. The which-key label blocks are untouched, since they use string key descriptions and never assumed the keymap existed. I byte-compiled all 24 files (no new free-variable warnings, because the cj/custom-keymap references are gone), and make test, validate-modules, and an init load all pass.
* refactor(load-graph): make hidden module dependencies explicitCraig Jennings12 days1-17/+13
| | | | | | | | | | | | | Phase 2 of the load-graph project. I fixed the seven hidden dependencies the classification surfaced, so each module declares what it uses instead of relying on init order. - system-defaults now requires host-environment and user-constants at runtime. They were eval-when-compile only, but env-bsd-p and user-home-dir are read at load, so the compiled module couldn't load standalone. - custom-buffer-file, dev-fkeys, calendar-sync, and video-audio-recording require keybindings and drop their (when (boundp 'cj/custom-keymap) ...) shims. The shim silently dropped the C-; binding when the module loaded before keybindings. The explicit require makes the dependency real. - flycheck-config and mail-config require keybindings for their cj/custom-keymap bindings (a use-package :map and a direct keymap-set). - Removed a dead eval-when-compile (defvar cj/custom-keymap) in transcription-config; nothing there used the variable. No init.el load-order change. keybindings and the foundation modules already load before these, so the requires are no-ops at startup and only fix standalone and test loading. I verified each fix with a fresh emacs --batch (require 'X), then swept all modules standalone: every one loads or fails only with a clear missing-package message. Full make test, make validate-modules, and an init smoke all pass. Module headers and the inventory's hidden-dependency section are updated to mark the seven resolved.
* docs(load-graph): classify domain, integration, and optional modulesCraig Jennings12 days1-1/+15
| | | | | | | | | | Eighth classification batch: 17 domain/integration/optional modules — ai-config, ai-vterm, browser-config, calendar-sync, calibredb-epub-config, chrono-tools, dirvish-config, dwim-shell-config, erc-config, eshell-config, eww-config, flyspell-and-abbrev, games-config, gloss-config, httpd-config, jumper, latex-config. I annotated each header, added a Batch 8 table to the inventory, and extended the validation allowlist. 82 of 102 modules are now classified. Almost all are eager only by init order and become command/hook/mode-loaded. calendar-sync stays eager when its .local.el is present. One new hidden dependency: calendar-sync guards its C-; g registration with a boundp shim and doesn't require keybindings, so the binding drops standalone. I deferred elfeed-config rather than annotate it. Its header edit triggers byte-compilation, and the existing tests only pass when the module loads as interpreted source — the compiled cj/elfeed-process-entries inlines an elfeed struct accessor the stubs can't intercept, and the batch test environment has no elfeed package to build real structs. It needs its tests rewritten first, recorded in the inventory and a new todo task. Also made the header allowlist scoping test durable: it used games-config (now classified) as its unclassified example; switched to a sentinel name plus a duplicate-entry guard.
* refactor(auth): consolidate the auth-source secret lookup into one helperCraig Jennings14 days1-8/+3
| | | | | | | | The auth-source-search + funcall-the-secret block was copied four times: calendar-sync--calendar-url, cj/auth-source-secret (ai-config), cj/--auth-source-password (transcription), and cj/slack--get-credential. Each searched authinfo, pulled :secret, and called it when the netrc backend returned a function. I pulled that into cj/auth-source-secret-value in system-lib (a leaf, so calendar-sync doesn't have to depend on ai-config and drag in the gptel stack). It takes an optional user and returns the secret or nil. The four callers now delegate to it: ai-config layers its required-secret error on top, and the others keep their nil-on-miss behavior. With the direct auth-source-search calls gone, I dropped the now-unused (require 'auth-source) from transcription, slack, and calendar-sync. The helper's autoload covers it. The transcription tests that exercise the delegated path stay green, and the primitive and the error wrapper get their own tests.
* feat(calendar-sync): resolve .ics feed URLs from auth-sourceCraig Jennings2026-05-211-5/+27
| | | | | | A calendar's .ics feed URL is a secret token, so I'd rather not keep it in a plaintext config file. A calendar can now name a :secret-host, and calendar-sync--calendar-url looks the URL up in auth-source (~/.authinfo.gpg) at sync time. Inline :url still works and wins when both are set, so existing configs are unaffected. I added 7 tests covering the explicit-url, string-secret, function-secret, precedence, and no-match paths, and switched the .example template to the :secret-host shape.
* feat(calendar-sync): dispatch Google calendars through API helperCraig Jennings2026-05-201-6/+110
| | | | | | | | | | The Python helper from d6a995b could fetch and render on its own, but nothing in Emacs called it. This wires it in. Each entry in calendar-sync-calendars now takes a :fetcher key. 'api routes through the helper, and the default 'ics keeps the existing curl + Elisp parser path. Proton and any plain .ics feed work unchanged because the key defaults to 'ics. The 'api path reads :account and :calendar-id off the calendar plist, builds the helper command (honoring the past/future window and the calendar-sync-skip-declined toggle), and runs it through make-process. The script writes the org file directly, so the sentinel only handles state bookkeeping and failure reporting, the same as the .ics worker. I split the old --sync-calendar body into --sync-calendar-ics and turned --sync-calendar into a dispatcher. The command builder and script-path resolution are pure functions, tested directly. The dispatch routing is tested with both leaf syncers stubbed, so no process runs. I added 14 tests across the two new files, and the full suite is green. Running the 'api path still needs the one-time OAuth bootstrap from docs/calendar-sync-api-setup.org.
* fix(calendar-sync): drop declined events from synced outputCraig Jennings2026-05-191-0/+25
| | | | | | | | | | | | | | | | The sync parsed PARTSTAT into a :STATUS: declined property but kept the event. Meetings I'd declined still landed in dcal.org / gcal.org and showed on the agenda. I added a pure --filter-declined helper called inside --parse-ics after event collection, plus the calendar-sync-skip-declined defvar (default t) so it can be flipped off without code changes. The .ics feed and the Calendar API can disagree on PARTSTAT. OOO auto-declines sometimes only write API-side, so a few declined events may still slip through. I'm calling this out because the filter looks absolute from the agenda but isn't. Tests cover Normal/Boundary/Error (11 cases). Full suite is green.
* refactor: consolidate runtime state into persist/Craig Jennings2026-05-161-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Six previously-scattered runtime state files now live under persist/ in user-emacs-directory: - theme-file (was .emacs-theme) - pdf-view-restore-filename (was .pdf-view-restore) - time-zones--city-list-file (was .time-zones.el) - calendar-sync--state-file (was data/calendar-sync-state.el) - prescient-save-file (was var/prescient-save.el) - org-id-locations-file (was .org-id-locations) The defaults in each module now expand to persist/<name> instead of the user-emacs-directory root or ad-hoc subdirs. Existing files moved into persist/ alongside this change so the next launch picks up the state without regenerating. test-ui-theme-default-theme-file-is-emacs-dotfile renamed to test-ui-theme-default-theme-file-is-under-persist and updated to assert the new default path. lsp-session-file is left at the root for now -- prog-lsp.el has no (require) reference anywhere, so the use-package block that would carry the redirect never runs. Tier 3 follow-up: confirm the module is dead, then delete it or wire it into the load chain. The var/ directory is now empty and removed. data/ retains the calendar agenda content (dcal/gcal/pcal.org) and the .rest API examples -- content, not state, stays where it is.
* fix(calendar-sync): give the no-init .ics worker its module load-pathCraig Jennings2026-05-111-1/+3
| | | | The async .ics-to-Org worker runs `emacs --batch --no-site-file --no-site-lisp' and loads `calendar-sync.el' by absolute path, but that doesn't make its sibling `(require 'cj-org-text-lib)' resolvable, so the conversion died with "Cannot open load file: cj-org-text-lib". `calendar-sync--worker-command' now inserts `-L <module-dir>' before `-l calendar-sync.el', which keeps the worker isolated from `init.el' while letting it load its local module deps. Updated the worker-command test and added a regression test that runs the real no-init worker shape.
* refactor(cj-org-text): rename to cj-org-text-lib for naming consistencyCraig Jennings2026-05-101-1/+1
| | | | | | | | | | | Same naming-convention fix as the cj-cache rename. Org-safe text sanitizers extracted in Phase 3 went into modules/cj-org-text.el, should have followed the established `-lib' suffix convention. Rename modules/cj-org-text.el -> modules/cj-org-text-lib.el; update provide form, file header, and the two (require 'cj-org-text) call sites in calendar-sync and test-cj-org-text-sanitize. No behavior change.
* refactor(cj-org-text): extract Org-safe text sanitizers from calendar-syncCraig Jennings2026-05-101-31/+7
| | | | | | | | | | | | | | Phase 3 of utility-consolidation. Three sanitizers moved from calendar-sync.el into a new cj-org-text.el module so other consumers (web-clipper, AI conversation, mail-to-org capture) can compose Org content from external text without depending on calendar: - `calendar-sync--sanitize-org-body' -> `cj/org-sanitize-body-text' - `calendar-sync--sanitize-org-property-value' -> `cj/org-sanitize-property-value' - `calendar-sync--sanitize-org-heading' -> `cj/org-sanitize-heading' The helpers stay pure (string in, string out, nil-safe) and have no Org-mode dependency, so they work in batch and in tests without loading Org. Migrate calendar-sync.el to use the new public names: drop the three local defuns, add `(require \='cj-org-text)', update the six call sites in `calendar-sync--make-event-entry'. Move the existing 17-test file to `tests/test-cj-org-text-sanitize.el', rename test names to match the new helpers, add 1 nil-input test for `cj/org-sanitize-heading' that wasn't in the original file. Total: 18 Normal/Boundary tests across the three helpers.
* Keep calendar sync off the UI threadCraig Jennings2026-05-101-57/+185
| | | | Move calendar feed conversion into an isolated batch Emacs worker so large parse/write cycles do not freeze interactive editing. Cover the worker command, isolated logging, quoted settings, and sync success/failure paths with focused ERTs.
* Make calendar sync startup safe without configCraig Jennings2026-05-041-14/+26
|
* fix: sanitize calendar event headings and property valuesCraig Jennings2026-05-031-5/+28
| | | | | | | | | | `calendar-sync--event-to-org` already cleaned the description body via `calendar-sync--sanitize-org-body`, but the event summary went into the heading line and the location, organizer, status, and URL went into the property drawer without sanitization. Any of those fields containing newlines could create extra Org headings, close the property drawer early with a stray `:END:`, or inject property-looking lines that the agenda would then parse as real properties. I added two helpers. `calendar-sync--sanitize-org-property-value` trims the input and collapses any run of whitespace or newlines into a single space. `calendar-sync--sanitize-org-heading` composes that over the existing body sanitizer so `*` sequences also become `-`. The event-to-org function now routes the summary through the heading sanitizer and each property value through the property sanitizer. I added regression tests across two files. `test-calendar-sync--sanitize-org-body.el` gets 4 new tests for the two helpers, covering newline flattening, leading-star replacement, structural-character flattening, and whitespace collapse. `test-calendar-sync--event-to-org.el` gets 2 new integration tests. A summary containing `\n** Hidden task` produces a single `* ` heading with the body inlined. A location containing `\n:END:\n* Not a real heading` collapses to a single property line with no extra `:END:` or heading injected. 515 calendar-sync tests pass together.
* refactor(calendar-sync): extract require-calendars guard from 4 locationsCraig Jennings2026-04-051-8/+10
| | | | | Replaced 4 copies of "if null calendars, warn" with a shared calendar-sync--require-calendars predicate.
* refactor(calendar-sync): extract RFC 5545 continuation line unfoldingCraig Jennings2026-04-051-17/+17
| | | | | Deduplicated the folded-line handling loop from get-property and get-all-property-lines into calendar-sync--unfold-continuation.
* refactor(calendar-sync): extract CN and email parsing from duplicated codeCraig Jennings2026-04-051-25/+19
| | | | | Extracted calendar-sync--extract-cn and calendar-sync--extract-email from identical logic in parse-attendee-line and parse-organizer.
* refactor(calendar-sync): remove dead function calendar-sync--timezone-nameCraig Jennings2026-04-051-5/+0
| | | | Function was defined but never called anywhere in the codebase.
* feat(music): add random-aware next/previous; refactor music + calendar-syncCraig Jennings2026-04-031-159/+69
| | | | | | | | | | Music: random mode now respected by next/previous keys. Previous navigates a 50-track play history ring buffer. Fixed playlist replacement bug. 24 new tests. Calendar-sync: consolidated duplicate parse functions, extracted timezone localization helper, unified expand-daily/monthly/yearly into parameterized function, removed dead code. 33 new characterization tests. -90 lines.
* fix(calendar-sync): handle variable-length date lists in RRULE UNTILCraig Jennings2026-03-091-3/+7
| | | | | | | date-to-time used (reverse date) which broke when RRULE UNTIL values were parsed as 5-element lists (year month day hour minute) from UTC timestamps. This caused recurring events with UTC UNTIL dates to expand to 0 occurrences, producing stale calendar entries.
* chore: rename chime.el references to ChimeCraig Jennings2026-02-231-1/+1
| | | | | Update load-path, GitHub URL, and all project/package name references to reflect the chime.el → Chime rename.
* perf(calendar-sync): replace shell-out timezone conversion with pure ElispCraig Jennings2026-02-141-32/+60
| | | | | | | | | convert-tz-to-local was spawning a `date` subprocess per timestamp, causing ~15s parse freezes on Proton (223 events) and ~10s on Google (1543 events). Replaced with encode-time/decode-time ZONE argument — same TZ database, no subprocess overhead. Parse times now 0.2-2.8s. Also adds phase timing instrumentation behind cj/debug-modules flag.
* fix(calendar-sync): increase fetch timeout for large calendarsCraig Jennings2026-02-061-1/+11
| | | | | | Google calendar (7k+ events, 4.5MB) was hitting the 30s hard-coded curl timeout. Use configurable calendar-sync-fetch-timeout (default 120s) with a separate 10s connect-timeout for fast failure on unreachable hosts.
* fix(calendar-sync): sanitize description text to prevent org heading corruptionCraig Jennings2026-02-061-2/+13
| | | | | | Event descriptions containing lines starting with * were being interpreted as org headings, breaking file structure. Replace leading asterisks with dashes before writing descriptions.
* fix(calendar-sync): fix heading order, continuation lines, and exception ↵Craig Jennings2026-02-051-6/+10
| | | | | | | | | | | | | | | | | | text cleaning Three bugs found during manual verification of calendar sync output: 1. Heading/timestamp order reversed in event-to-org — nreverse pattern put timestamp before the org heading. Swap initial list order. 2. get-property continuation line regex broken — the ^ anchor in "^\n[ \t]" prevented matching at the correct position, truncating long DESCRIPTION values at the first ICS line fold. Remove anchor, add explicit position check (matching get-all-property-lines pattern). 3. collect-recurrence-exceptions didn't clean text — exception instances got raw ICS text (literal \n, HTML tags) replacing the cleaned base event text. Wrap summary/description/location in clean-text.
* feat(calendar-sync): add event details — attendees, organizer, status, URLCraig Jennings2026-02-051-16/+208
| | | | | | | | Add ICS text unescaping (RFC 5545), HTML stripping, and new fields (attendees/status, organizer, meeting URL) to calendar-sync.el. event-to-org now outputs org property drawers. 88 new tests across 10 test files, 146/146 pass. Also fix pre-existing test require order and keymap guard issues.
* refactor(calendar): move calendar URLs into calendar-sync.elCraig Jennings2026-02-041-0/+12
| | | | | Consolidate calendar configuration within the module itself rather than requiring setup in init.el. Improves module encapsulation.
* feat(calendar-sync): add EXDATE support for excluded recurring event datesCraig Jennings2026-02-031-10/+135
| | | | | | | | | | | | | | | | | | | | | When someone deletes a single instance of a recurring meeting in Google Calendar, the calendar exports an EXDATE property marking that date as excluded. Previously, calendar-sync expanded the RRULE without filtering out these excluded dates, causing deleted instances to appear in org output. New functions: - calendar-sync--get-exdates: Extract all EXDATE values from event - calendar-sync--get-exdate-line: Get full EXDATE line with parameters - calendar-sync--parse-exdate: Parse EXDATE into datetime list - calendar-sync--collect-exdates: Collect excluded dates with TZ conversion - calendar-sync--exdate-matches-p: Check if occurrence matches an EXDATE - calendar-sync--filter-exdates: Filter out excluded dates from occurrences Modified calendar-sync--expand-recurring-event to collect and filter EXDATEs after RRULE expansion. Includes 47 new tests covering extraction, parsing, collection, filtering, and integration with RECURRENCE-ID exceptions.
* feat(calendar-sync): add RECURRENCE-ID exception handling for recurring eventsCraig Jennings2026-02-031-10/+178
| | | | | | | | | | | | | | | | | | | | | Handle rescheduled instances of recurring calendar events by processing RECURRENCE-ID properties from ICS files. When someone reschedules a single instance of a recurring meeting in Google Calendar, the calendar-sync module now shows the rescheduled time instead of the original RRULE time. New functions: - calendar-sync--get-recurrence-id: Extract RECURRENCE-ID from event - calendar-sync--get-recurrence-id-line: Get full line with TZID params - calendar-sync--parse-recurrence-id: Parse into (year month day hour minute) - calendar-sync--collect-recurrence-exceptions: Collect all exceptions by UID - calendar-sync--occurrence-matches-exception-p: Match occurrences to exceptions - calendar-sync--apply-single-exception: Apply exception data to occurrence - calendar-sync--apply-recurrence-exceptions: Apply all exceptions to occurrences Also adds DeepSat calendar configuration (dcal-file) to user-constants, init.el, and org-agenda-config. 48 unit and integration tests added covering normal, boundary, and error cases.
* feat(calendar-sync): add timezone conversion for TZID-qualified eventsCraig Jennings2026-02-011-12/+79
| | | | | | | | | | | | Events with TZID parameters (e.g., DTSTART;TZID=Europe/Lisbon) were displaying in the source timezone instead of local time. Added: - calendar-sync--extract-tzid: extracts TZID from property lines - calendar-sync--convert-tz-to-local: converts using date command - Modified parse-timestamp to accept optional TZID parameter - Modified parse-event to extract and pass TZID through pipeline Includes 40 new tests covering extraction, conversion, and integration.
* feat(calendar-sync): re-enable auto-sync on startupCraig Jennings2026-01-271-1/+1
| | | | Freeze bugs are fixed; safe to auto-sync again.
* fix(calendar-sync): resolve freeze on DST transitions and large ICS filesCraig Jennings2026-01-271-7/+13
| | | | | | | | | | | Two bugs caused Emacs to freeze during calendar sync: 1. split-events used catastrophic regex (\(.\|\n\)*?) on multi-MB ICS data. Replaced with buffer-based search-forward (0.011s for 4.5MB). 2. add-days used midnight for date arithmetic. On DST fall-back days, adding 86400s to midnight CDT yields 11pm CST (same date), creating an infinite loop. Fixed by using noon so ±1h DST shift stays correct.
* fix(calendar-sync): disable auto-start to prevent freezeCraig Jennings2026-01-141-1/+1
| | | | | | Proton calendar download causes Emacs to freeze. Disabled auto-sync by default until root cause is investigated. Manual sync still available via C-; g s keybinding.
* feat(calendar-sync): multi-calendar support with property testsCraig Jennings2025-12-021-73/+179
| | | | | | Added multi-URL calendar sync supporting Google and Proton calendars. Each calendar syncs to separate file with per-calendar state tracking. Added 13 property-based tests for RRULE expansion. Total: 150 tests passing.
* fix: add quick-sdcv quit binding and fix calendar-sync sentinelCraig Jennings2025-11-211-11/+13
| | | | | | | - Add 'q' keybinding in quick-sdcv-mode to quit-window for easier dictionary dismissal while reading epubs - Fix "Selecting deleted buffer" error in calendar-sync by checking buffer-live-p before accessing process buffer in sentinel
* feat(calendar-sync): Add RRULE support and refactor expansion functionsCraig Jennings2025-11-181-35/+385
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Implements complete recurring event (RRULE) expansion for Google Calendar with rolling window approach and comprehensive test coverage. Features: - RRULE expansion for DAILY, WEEKLY, MONTHLY, YEARLY frequencies - Support for INTERVAL, BYDAY, UNTIL, and COUNT parameters - Rolling window: -3 months to +12 months from current date - Fixed COUNT parameter bug (events no longer appear beyond their limit) - Fixed TZID parameter parsing (supports timezone-specific timestamps) - Replaced debug messages with cj/log-silently Refactoring: - Extracted helper functions to eliminate code duplication: - calendar-sync--date-to-time: Date to time conversion - calendar-sync--before-date-p: Date comparison - calendar-sync--create-occurrence: Event occurrence creation - Refactored all expansion functions to use helper functions - Reduced code duplication across daily/weekly/monthly/yearly expansion Testing: - 68 tests total across 5 test files - Unit tests for RRULE parsing, property extraction, weekly expansion - Integration tests for complete RRULE workflow - Tests for helper functions validating refactored code - All tests passing
* fix: increase calendar-sync curl timeout from 10 to 30 secondsCraig Jennings2025-11-171-1/+1
| | | | | | 10-second timeout was too aggressive for slower networks or delayed Google servers. Increased to 30 seconds to prevent timeout errors while still preventing indefinite hangs.
* feat(calendar-sync): Make ICS fetching asynchronousCraig Jennings2025-11-171-29/+42
| | | | | | | | | | | | | | Changed calendar-sync--fetch-ics from synchronous call-process to asynchronous make-process with callback pattern. This prevents Emacs from freezing during calendar syncs. Changes: - calendar-sync--fetch-ics now takes a callback parameter - Uses make-process with sentinel for async completion - calendar-sync-now updated to use callback pattern - Fetch completes in background without blocking Emacs All 56 tests pass. User confirmed improved responsiveness.
* chore(calendar-sync): Change default interval to 60 minutesCraig Jennings2025-11-171-3/+3
| | | | | | | | | | Changed default sync interval from 15 minutes to 60 minutes (1 hour). Rationale: - Calendar events typically don't change that frequently - Reduces network requests and potential blocking events - Users can still manually sync or adjust interval as needed - More conservative default that balances freshness with performance
* refactor(calendar-sync): Make interval configurable in minutesCraig Jennings2025-11-171-11/+12
| | | | | | | | | | | | | | | | | | | Changed calendar-sync-interval (seconds) to calendar-sync-interval-minutes for more user-friendly configuration. Changes: - Renamed: calendar-sync-interval → calendar-sync-interval-minutes - Units: seconds → minutes (default: 15) - Internal conversion to seconds happens in calendar-sync-start - Updated docstrings and messages to reference minutes Benefits: - More intuitive configuration (users think in minutes, not seconds) - Clearer variable name indicates units - No functional change, just better UX Example usage: (setq calendar-sync-interval-minutes 30) ; Sync every 30 minutes
* feat(calendar-sync): Add auto-start toggle and convert to defvarCraig Jennings2025-11-171-20/+17
| | | | | | | | | | | | | | | | | | | | | | | | Changes: 1. Converted defcustom → defvar (3 variables): - calendar-sync-ics-url - calendar-sync-interval - calendar-sync-file - Removed defgroup (not using customize system) 2. Added calendar-sync-auto-start variable (default: t) - Controls whether auto-sync starts when module loads - Set to nil to disable automatic startup 3. Updated initialization to check auto-start flag - Auto-sync only starts if both auto-start and URL are non-nil - Provides user control over automatic behavior Rationale: - Auto-sync is convenient but should be toggleable - defvar is simpler and more direct than defcustom - Consistent with project style (no customize interface) Default behavior: Auto-sync enabled (backwards compatible with user expectation)
* fix(calendar-sync): Remove carriage return characters from synced eventsCraig Jennings2025-11-171-2/+16
| | | | | | | | | | | | | | | | | | | | | | | Problem: Google Calendar .ics files use CRLF line endings (RFC 5545 spec), which resulted in 11,685 ^M (CR) characters appearing in gcal.org, particularly at the end of org header lines. Solution: - Created calendar-sync--normalize-line-endings function to strip all \r characters from .ics content - Integrated into calendar-sync--fetch-ics immediately after curl download - Ensures clean Unix LF-only line endings throughout parsing pipeline Testing: - Added comprehensive test suite: test-calendar-sync--normalize-line-endings.el - 16 tests covering Normal, Boundary, and Error cases - All 56 existing calendar-sync tests still pass (no regressions) - Verified: gcal.org now has 0 CR characters (was 11,685) Files modified: - modules/calendar-sync.el: Added normalize function, updated fetch function - tests/test-calendar-sync--normalize-line-endings.el: New comprehensive test suite
* feat(calendar-sync): Add automatic timezone detection and chronological sortingCraig Jennings2025-11-161-0/+438
Implemented calendar-sync.el as a complete replacement for org-gcal, featuring: **Core Functionality:** - One-way sync from Google Calendar to Org (via .ics URL) - UTC to local timezone conversion for all event timestamps - Chronological event sorting (past → present → future) - Non-blocking sync using curl (works reliably in daemon mode) **Automatic Timezone Detection:** - Detects timezone changes when traveling between timezones - Tracks timezone offset in seconds (-21600 for CST, -28800 for PST, etc.) - Triggers automatic re-sync when timezone changes detected - Shows informative messages: "Timezone change detected (UTC-6 → UTC-8)" **State Persistence:** - Saves sync state to ~/.emacs.d/data/calendar-sync-state.el - Persists timezone and last sync time across Emacs sessions - Enables detection even after closing Emacs before traveling **User Features:** - Interactive commands: calendar-sync-now, calendar-sync-start/stop - Keybindings: C-; g s (sync), C-; g a (start auto-sync), C-; g x (stop) - Optional auto-sync every 15 minutes (disabled by default) - Clear status messages for all operations **Code Quality:** - Comprehensive test coverage: 51 ERT tests (100% passing) - Refactored UTC conversion into separate function - Clean separation of concerns (parsing, conversion, formatting, sorting) - Well-documented with timezone behavior guide and changelog **Migration:** - Removed org-gcal-config.el (archived in modules/archived/) - Updated init.el to use calendar-sync - Moved gcal.org to .emacs.d/data/ for machine-independent syncing - Removed org-gcal appointment capture template Files modified: modules/calendar-sync.el:442, tests/test-calendar-sync.el:577 Files created: data/calendar-sync-state.el, tests/testutil-calendar-sync.el Documentation: docs/calendar-sync-timezones.md, docs/calendar-sync-changelog.md