| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
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.
|
| | |
|
| |
|
|
|
|
|
|
|
|
| |
`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.
|
| |
|
|
|
| |
Replaced 4 copies of "if null calendars, warn" with a shared
calendar-sync--require-calendars predicate.
|
| |
|
|
|
| |
Deduplicated the folded-line handling loop from get-property and
get-all-property-lines into calendar-sync--unfold-continuation.
|
| |
|
|
|
| |
Extracted calendar-sync--extract-cn and calendar-sync--extract-email from
identical logic in parse-attendee-line and parse-organizer.
|
| |
|
|
| |
Function was defined but never called anywhere in the codebase.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
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.
|
| |
|
|
|
| |
Update load-path, GitHub URL, and all project/package name
references to reflect the chime.el → Chime rename.
|
| |
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
Event descriptions containing lines starting with * were being interpreted
as org headings, breaking file structure. Replace leading asterisks with
dashes before writing descriptions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
| |
Consolidate calendar configuration within the module itself rather
than requiring setup in init.el. Improves module encapsulation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
Freeze bugs are fixed; safe to auto-sync again.
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
| |
- 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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
|
|
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
|