diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-20 15:38:23 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-20 15:38:23 -0500 |
| commit | 1ccd7bddf55c41202afe316e70fa01059e1bcbbb (patch) | |
| tree | fee8bbe60482ecd7fba283a0e1aba9927db6aa6d | |
| parent | 404590623599398f120cd73d44ad235271c3b5bd (diff) | |
| download | dotemacs-1ccd7bddf55c41202afe316e70fa01059e1bcbbb.tar.gz dotemacs-1ccd7bddf55c41202afe316e70fa01059e1bcbbb.zip | |
fix(agenda): spawn the agenda frame tiled, not compositor-fullscreen
I dropped (fullscreen . fullboth) from the spawned frame so a tiling WM places it side by side with the working frame. "Fullscreen" was the wrong word for the intent: a normal frame at its full tiled position, not one covering the whole output. The engage-routing and focus logic are unchanged and matter more now, since a task opens in the adjacent working pane.
| -rw-r--r-- | docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org | 3 | ||||
| -rw-r--r-- | init.el | 2 | ||||
| -rw-r--r-- | modules/org-agenda-config.el | 2 | ||||
| -rw-r--r-- | modules/org-agenda-frame.el | 32 | ||||
| -rw-r--r-- | tests/test-org-agenda-frame.el | 10 |
5 files changed, 34 insertions, 15 deletions
diff --git a/docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org b/docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org index d6a8d295..567da53b 100644 --- a/docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org +++ b/docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org @@ -8,6 +8,7 @@ :PROPERTIES: :ID: 7705c94b-9bb7-47d8-9828-e9584172c54f :END: +- 2026-07-20 Mon @ 15:33 -0500 — post-implementation correction: dropped =(fullscreen . fullboth)= from the spawned frame. Craig's "fullscreen" meant a normal frame at its full tiled position, not a compositor-wide fullscreen; the frame is now a plain =make-frame= that a tiling WM (Hyprland) places side by side with the working frame. The engage-routing and focus logic are unchanged and now matter more (open a task in the adjacent working pane). The spec's "fullscreen" wording and filename are retained as historical; read them as "dedicated tiled frame". Covered by =test-org-agenda-frame-parameters-normal-tiled-frame=. - 2026-07-20 Mon @ 14:10 -0500 — IMPLEMENTED. Built both phases in =modules/org-agenda-frame.el= (58 ERT tests, full suite green, byte-compile clean, full init loads clean, live-reloaded into the daemon with all wiring confirmed). Phase 1: frame lookup/predicate/working-frame routing, the =F= today-anchored 7-day view + registration, the default-deny =cj/agenda-frame-mode= (allowlist + two message classes + menu removal + finalize re-enable), transactional spawn/raise/delete/toggle, engage routing, sticky/close lifecycle. Phase 2: the 5-min wall-clock =org-agenda-redo= timer with the window/focus contract, duplicate-timer prevention, deterministic point restoration, the frame-owned last-good snapshot with cloned markers + failure overlay + report-once latch, and the public =cj/agenda-frame-toggle= on =S-<f8>= with the force-rescan moved to =C-M-<f8>=. The compositor fullscreen/focus and real-redo behaviors are a residual manual check (VERIFY filed under Manual testing). - 2026-07-20 Mon @ 13:30 -0500 — DOING. Decomposed into build tasks (Phase 1, Phase 2, flip-to-IMPLEMENTED) under the fullscreen-frame PROJECT in todo.org; =:SPEC_ID:= stamped. Building Phase 1. - 2026-07-20 Mon @ 13:26 -0500 — READY (Craig accepted for build). Folded Codex's marker-clone finding (all 28 findings, 13 decisions resolved). Craig's call: build v1 now; the Hyprland-window variant is vNext once the in-Emacs frame proves out. Decomposing into build tasks next. @@ -67,7 +68,7 @@ Craig wants a second, non-modal surface: the agenda living in its own fullscreen * Design -The agenda lives in its own frame of the running daemon, created with =make-frame= carrying =(fullscreen . fullboth)= and a marker parameter (=(cj/agenda-frame . t)=) so the toggle can find, raise, or delete it. Sharing the daemon means the frame sees the same live state as every working frame: calendar-sync writes, unsaved buffer edits, and the current now-line are all already there, so "live" needs only a periodic redo, not a reload. +The agenda lives in its own frame of the running daemon, created with =make-frame= carrying a marker parameter (=(cj/agenda-frame . t)=) so the toggle can find, raise, or delete it. (The frame was originally spawned =(fullscreen . fullboth)=; that was dropped 2026-07-20 so a tiling WM places it side by side with the working frame rather than covering the whole output — see the status history.) Sharing the daemon means the frame sees the same live state as every working frame: calendar-sync writes, unsaved buffer edits, and the current now-line are all already there, so "live" needs only a periodic redo, not a reload. For the user, the interface is one key. =S-<f8>= toggles the frame: spawn it fullscreen if none exists, raise and select it if it exists but isn't focused, delete it if it's the selected frame. The current occupant of =S-<f8>= (=cj/org-agenda-refresh-files=, the manual force-rescan shipped in 17ae3e2a) moves to =C-M-<f8>=, keeping the whole force-refresh idea in the F8 family. @@ -129,7 +129,7 @@ (require 'org-config) ;; basic org-mode settings (require 'org-faces-config) ;; custom themeable faces for agenda keywords + priorities (require 'org-agenda-config) ;; agenda, task tracking, and notifications -(require 'org-agenda-frame) ;; S-<f8> dedicated fullscreen agenda frame +(require 'org-agenda-frame) ;; S-<f8> dedicated agenda frame (tiled beside the working frame) (require 'org-babel-config) ;; org-mode prog blocks; literate programming (require 'org-capture-config) (require 'org-contacts-config) ;; fully integrated org-mode contacts management diff --git a/modules/org-agenda-config.el b/modules/org-agenda-config.el index 7bbb31ae..1e91fa48 100644 --- a/modules/org-agenda-config.el +++ b/modules/org-agenda-config.el @@ -232,7 +232,7 @@ agenda-frame toggle and moved the force-rescan here." (interactive) (cj/build-org-agenda-list 'force-rebuild)) ;; S-<f8> and C-M-<f8> are bound by `org-agenda-frame.el' (cj/--agenda-frame-install-keys): -;; S-<f8> toggles the fullscreen agenda frame; C-M-<f8> runs the force-rescan above. +;; S-<f8> toggles the dedicated agenda frame; C-M-<f8> runs the force-rescan above. (defun cj/todo-list-all-agenda-files () "Displays an \\='org-agenda\\=' todo list. diff --git a/modules/org-agenda-frame.el b/modules/org-agenda-frame.el index e4b60505..9ac02e4d 100644 --- a/modules/org-agenda-frame.el +++ b/modules/org-agenda-frame.el @@ -1,4 +1,4 @@ -;;; org-agenda-frame.el --- Dedicated fullscreen agenda frame -*- lexical-binding: t; -*- +;;; org-agenda-frame.el --- Dedicated agenda frame -*- lexical-binding: t; -*- ;; author: Craig Jennings <c@cjennings.net> ;;; Commentary: @@ -10,9 +10,10 @@ ;; Runtime requires: none. ;; Direct test load: yes. ;; -;; A dedicated fullscreen Emacs frame of the running daemon that shows a -;; today-anchored seven-day org-agenda, refreshing itself, kept read-only and -;; focus-locked. Spawned/raised/closed by one key. See the spec: +;; A dedicated Emacs frame of the running daemon that shows a today-anchored +;; seven-day org-agenda, refreshing itself, kept read-only and focus-locked. +;; A normal (non-fullscreen) frame, so a tiling WM places it side by side with +;; the working frame. Spawned/raised/closed by one key. See the spec: ;; docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org. ;; ;; Phase 1 (this pass) builds non-interactive helpers only: frame lookup, @@ -171,7 +172,7 @@ Signals a `user-error' when point is not on an agenda item." (defun cj/--agenda-frame-close () "Close the agenda frame from within it. Bound to q, Q, and x so Org's own quit keys delete the whole frame -\(and cancel its timer) rather than leaving a sole-window fullscreen +\(and cancel its timer) rather than leaving a sole-window agenda frame stranded on a non-agenda buffer." (interactive) (cj/--agenda-frame-delete)) @@ -328,8 +329,17 @@ the timer and kills the sticky buffer." (select-frame-set-input-focus frame) frame) +(defun cj/--agenda-frame-make-parameters () + "Return the frame parameters for the dedicated agenda frame. +A normal frame -- not fullscreen -- so a tiling window manager places it +side by side with the working frame rather than covering the whole output. +It carries the `cj/agenda-frame' marker and a distinct name so window-manager +rules can target it." + `((,cj/--agenda-frame-parameter . t) + (name . "Org Agenda"))) + (defun cj/--agenda-frame-spawn () - "Create, display, and focus the dedicated fullscreen agenda frame. + "Create, display, and focus the dedicated agenda frame. Transactional: on any failure after `make-frame', delete the partial frame (which cleans up its buffer and timer via the delete hook), restore focus to the launching frame, and signal a `user-error' naming the cause. @@ -339,9 +349,7 @@ Returns the new agenda frame on success." (condition-case err (progn (setq cj/--agenda-frame-launch-frame launch) - (setq frame (make-frame `((,cj/--agenda-frame-parameter . t) - (fullscreen . fullboth) - (name . "Org Agenda")))) + (setq frame (make-frame (cj/--agenda-frame-make-parameters))) (select-frame-set-input-focus frame) ;; Cached, non-forced: a frame spawned early after daemon startup ;; still shows the full project agenda, not the base-files-only view. @@ -605,9 +613,9 @@ Returns the timer." ;; -- Public command and key install ------------------------------------------ (defun cj/agenda-frame-toggle () - "Toggle the dedicated fullscreen agenda frame. -Spawn it fullscreen when none exists, raise and focus it when it exists -but is unfocused, and close it when it is the selected frame." + "Toggle the dedicated agenda frame. +Spawn it when none exists, raise and focus it when it exists but is +unfocused, and close it when it is the selected frame." (interactive) (cj/--agenda-frame-toggle)) diff --git a/tests/test-org-agenda-frame.el b/tests/test-org-agenda-frame.el index 2b9dfcfc..48ac1594 100644 --- a/tests/test-org-agenda-frame.el +++ b/tests/test-org-agenda-frame.el @@ -700,6 +700,16 @@ Guards against restoring by raw line number after the item moved." (call-interactively 'cj/agenda-frame-toggle) (should called)))) +(ert-deftest test-org-agenda-frame-parameters-normal-tiled-frame () + "Boundary: the agenda frame is a normal frame (the tiling WM places it +side by side with the working frame), carrying the marker and a distinct name." + (let ((params (cj/--agenda-frame-make-parameters))) + (should (assq cj/--agenda-frame-parameter params)) + (should (equal (cdr (assq 'name params)) "Org Agenda")) + ;; No fullscreen request -- a fullboth frame would cover the whole output + ;; instead of tiling beside the working frame. + (should-not (assq 'fullscreen params)))) + (ert-deftest test-org-agenda-frame-spawn-starts-timer () "Normal: a successful spawn starts the refresh timer for the new frame." (let (timed) |
