<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/modules/user-constants.el, branch main</title>
<subtitle>My Emacs configuration
</subtitle>
<id>https://git.cjennings.net/dotemacs/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/dotemacs/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/'/>
<updated>2026-05-26T00:11:00+00:00</updated>
<entry>
<title>feat(user-constants): make required-path init failures actionable</title>
<updated>2026-05-26T00:11:00+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-26T00:11:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=a09398f27f1e93bfbe84e1dd185e70b391e82888'/>
<id>urn:sha1:a09398f27f1e93bfbe84e1dd185e70b391e82888</id>
<content type='text'>
cj/verify-or-create-dir and cj/verify-or-create-file caught every creation failure and only messaged it, so a broken environment for a path the config actually needs stayed quiet until some later module failed in a more confusing way. I gave both an optional required flag and routed failures through a shared cj/--report-path-failure: a required failure raises a prominent display-warning, an optional one is still just logged so it never blocks startup.

The initializer now groups its paths by that distinction. Required: the backbone directories (sync, org, roam) and the calendar stubs (gcal/pcal/dcal), since org-agenda-list hangs prompting for those when they're missing. Optional: the secondary dirs and the content files, each populated by its own workflow. I went with a warning rather than a user-error for required failures so a directory hiccup surfaces loudly without aborting init.

Added error-path tests: an optional failure logs and never warns, and a required dir or file failure raises a user-constants warning.
</content>
</entry>
<entry>
<title>refactor(user-constants): move filesystem creation out of module load</title>
<updated>2026-05-26T00:06:13+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-26T00:06:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=423ca0e8e502661343a8aa02d5c58c5029e40b03'/>
<id>urn:sha1:423ca0e8e502661343a8aa02d5c58c5029e40b03</id>
<content type='text'>
(require 'user-constants) created ~8 directories and ~10 org/calendar files at load time, via a top-level dolist for the calendar stubs and a top-level call to cj/initialize-user-directories-and-files. That meant any bare require — tests, byte-compile, batch tools — wrote to disk. It's why a stray sync/org/ tree kept appearing in the repo during test runs.

I removed both top-level forms and folded the gcal/pcal/dcal creation into the initializer. The path defconsts stay exactly as they were, so every consumer that just reads a path is unaffected. init.el now calls the initializer right after requiring the module, guarded by (unless noninteractive), so interactive and daemon startup create everything in the same order as before while a bare require stays side-effect-free.

Added tests/test-user-constants.el: loading the module creates nothing, and the initializer creates the backbone dirs and the configured files. Updated the module header — top-level side effects are now none and it's safe to load in tests.
</content>
</entry>
<entry>
<title>docs(load-graph): seed module inventory and annotate foundation headers</title>
<updated>2026-05-24T21:13:01+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-24T21:13:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2d83f8f55977dbf69f4f80e490ed0b96aeea4eee'/>
<id>urn:sha1:2d83f8f55977dbf69f4f80e490ed0b96aeea4eee</id>
<content type='text'>
I started the init.el load-graph classification with the foundation batch. I added docs/design/module-inventory.org as the living per-module inventory and annotated the seven foundation modules (system-lib, user-constants, host-environment, system-defaults, keyboard-compat, keybindings, config-utilities) with the load-graph header contract: layer, category, load shape, eager reason, top-level side effects, runtime requires, and direct-test-load safety.

I changed no load order, so init.el keeps its current eager order. The inventory records one hidden dependency for Phase 2: system-defaults uses host-environment and user-constants symbols at load while declaring them eval-when-compile, so the compiled module cannot load standalone.
</content>
</entry>
<entry>
<title>refactor(foundation): hygiene pass across early-init, user-constants, system-defaults, chrono-tools</title>
<updated>2026-05-16T07:35:38+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T07:35:38+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=1c5a2ebab7c721d795ed9331afdb305fd683e172'/>
<id>urn:sha1:1c5a2ebab7c721d795ed9331afdb305fd683e172</id>
<content type='text'>
Six small fixes the 2026-05-15 module-by-module re-review surfaced:

- Consolidate `user-home-dir` -- canonical defconst stays in
  early-init.el (package-archive bootstrap needs it before normal
  modules load); user-constants.el switches to a `defvar` with the
  identical `(getenv "HOME")` expression so the module still loads /
  byte-compiles standalone, but at runtime early-init's defconst
  wins.
- Drop the redundant `(autoload 'env-bsd-p ...)` line in
  system-defaults.el.  The `(eval-when-compile (require
  'host-environment))` already exposes the symbol to the byte
  compiler, and at runtime host-environment is loaded earlier in
  init.el.  Added a comment documenting the boundary.
- Convert `cj/debug-modules` and `cj/use-online-repos` from `defvar`
  to `defcustom`, with `:type`, `:group 'cj`, and a top-level
  `(defgroup cj ...)` so both show up in M-x customize.
- Name the package-archive priorities in early-init.el.  Nine new
  defconsts replace the magic numbers (200 / 125 / 120 / 115 / 100 /
  25 / 20 / 15 / 5) with one constant each, plus a header comment
  explaining the local-first ordering and the gnu &gt; nongnu &gt; melpa &gt;
  melpa-stable trust ranking within each tier.
- Delete the 19-line commented-out `use-package time` world-clock
  block in chrono-tools.el.  `time-zones` immediately above is the
  active replacement; git history preserves the old config if anyone
  needs it.
- Add coverage for `cj/tmr-select-sound-file`.  Collapsed the
  prefix-arg branch into a delegation to
  `cj/tmr-reset-sound-to-default` (single reset source) and
  extracted `cj/tmr--available-sound-files` as a pure helper that
  tests directly.  9 ERT tests across Normal / Boundary / Error
  cover the available-sounds helper, the reset path, the prefix-arg
  delegation (no prompt), the normal selection path, and the
  empty-dir / missing-dir / cancel boundaries.
</content>
</entry>
<entry>
<title>feat(transcription): extend dired T to transcribe videos via ffmpeg, with tests</title>
<updated>2026-05-14T19:09:16+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-14T19:09:16+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=4a7529393ebbbc225939f21be783ec624d0a8168'/>
<id>urn:sha1:4a7529393ebbbc225939f21be783ec624d0a8168</id>
<content type='text'>
Pressing `T' in dired/dirvish on an audio file already transcribed
it; on a video file it bounced with "Not an audio file".  Real
recordings ship as .mp4 / .mkv at least as often as raw .m4a, so
the one-key flow ended at the wrong place.

Pipeline now:
- audio path -&gt; direct into `cj/--start-transcription-process'
  (unchanged).
- video path -&gt; async ffmpeg extracts the audio track to a temp
  .mp3 under `temporary-file-directory' (libmp3lame, VBR q:a 4,
  ~165kbps -- right size for speech, accepted by every backend),
  then transcribes that file with the temp marked for cleanup
  after the transcription sentinel fires.

Surface changes:
- `cj/video-file-extensions' added to user-constants.el (mp4, mkv,
  mov, webm, avi, m4v, wmv, flv, mpg, mpeg, 3gp, ogv).
- New predicates `cj/--video-file-p' / `cj/--media-file-p'.
- New `cj/--extract-audio-from-video' (async ffmpeg with success
  callback; surfaces `cj/--notify' on failure; user-errors if
  ffmpeg isn't on PATH).
- `cj/--start-transcription-process' gains optional `cleanup-file'.
  Sentinel deletes it after the existing logic runs.  Backwards
  compatible -- the audio flow doesn't pass it.
- `cj/transcribe-audio' renamed to `cj/transcribe-media' (dispatcher
  on audio vs video).  `cj/transcribe-audio-at-point' renamed to
  `cj/transcribe-media-at-point'.  Both old names kept as
  `defalias' so M-x history and any external references still work.
- `T' in dired-mode-map + dirvish-mode-map points at
  `cj/transcribe-media-at-point'.
- Module commentary USAGE block updated.

15 new ERT tests in `tests/test-transcription-video.el' cover the
predicates (happy/boundary/error), ffmpeg invocation (correct args
+ missing-ffmpeg path), the dispatcher (audio direct, video via
extraction, non-media rejected), the aliases, and the T binding.
One existing test in `test-transcription-status-and-commands.el'
updated to stub the new delegate name.

Verified locally that ffmpeg is on PATH with libmp3lame, and that
the exact arg list my code uses produces a valid MP3 from a
synthetic test video.
</content>
</entry>
<entry>
<title>fix(user-constants): create calendar data files on first launch</title>
<updated>2026-02-21T21:17:16+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-02-21T21:17:16+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=39e0b989ad1fd43c4632d267754eb3621dc3e278'/>
<id>urn:sha1:39e0b989ad1fd43c4632d267754eb3621dc3e278</id>
<content type='text'>
org-agenda-list prompts interactively for missing files, which hangs
the async subprocess chime uses to fetch events. Create empty
placeholders at init so calendar-sync can populate them on first sync.
</content>
</entry>
<entry>
<title>chore(yasnippet): move snippets into emacs.d for source control</title>
<updated>2026-02-16T11:14:28+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-02-16T11:14:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=bd3a6511c119464803f8e0bb142519ece2114327'/>
<id>urn:sha1:bd3a6511c119464803f8e0bb142519ece2114327</id>
<content type='text'>
Relocate snippets-dir from ~/sync/org/snippets/ to ~/.emacs.d/snippets/
and restore 28 snippet files from backup.
</content>
</entry>
<entry>
<title>feat(hugo): extract hugo-config module with C-; h keybindings</title>
<updated>2026-02-14T06:06:45+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-02-14T06:06:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=cb44e9cae2284585cf77f9cf6927947c066db45d'/>
<id>urn:sha1:cb44e9cae2284585cf77f9cf6927947c066db45d</id>
<content type='text'>
Standalone module for ox-hugo blog workflow. One-file-per-post
structure with keybindings for new post, export, open dir (dirvish
and system file manager), and toggle draft.
</content>
</entry>
<entry>
<title>feat(calendar-sync): add RECURRENCE-ID exception handling for recurring events</title>
<updated>2026-02-03T13:39:50+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-02-03T13:39:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=d0f928114f096af39c8231b7c4a9c9b05fc45f08'/>
<id>urn:sha1:d0f928114f096af39c8231b7c4a9c9b05fc45f08</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(calendar-sync): multi-calendar support with property tests</title>
<updated>2025-12-02T13:55:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2025-12-02T13:55:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=703bc366cf8439a398362d98bef513ef98942fd4'/>
<id>urn:sha1:703bc366cf8439a398362d98bef513ef98942fd4</id>
<content type='text'>
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.
</content>
</entry>
</feed>
