aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/specs/2026-07-17-org-agenda-fullscreen-frame-spec.org3
-rw-r--r--init.el2
-rw-r--r--modules/org-agenda-config.el2
-rw-r--r--modules/org-agenda-frame.el32
-rw-r--r--tests/test-org-agenda-frame.el10
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.
diff --git a/init.el b/init.el
index 761909f2..f4355820 100644
--- a/init.el
+++ b/init.el
@@ -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)