From 36e441a9ffd2c7d492a19ad4a916b7e2d0809b6f Mon Sep 17 00:00:00 2001 From: bw Date: Thu, 19 Feb 2026 00:57:42 -0500 Subject: [PATCH 1/4] hywiki.el - Add insertion of HyWikiWord#section reference links README.md - Add article link section. --- ChangeLog | 32 +++++++++ README.md | 24 +++++++ hasht.el | 22 +++--- hbut.el | 7 +- hibtypes.el | 14 ++-- hsys-consult.el | 4 +- hui-mini.el | 6 +- hui.el | 8 ++- hywiki.el | 173 ++++++++++++++++++++++++++++++++++----------- man/hyperbole.texi | 9 +-- 10 files changed, 231 insertions(+), 68 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6204b640..dea4faf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2026-02-18 Bob Weiner + +* hui.el (require 'hywiki): + (hui:link-possible-types): Add 'link-to-wikiword' clause. +* hbut.el (ibut:insert-text): Add 'actypes::link-to-wikiword' clause. +* man/hyperbole.texi (By Dragging): Add HyWikiWord Reference to referent + context table. + +* hywiki.el (hywiki-consult-file-and-line): Add. + (hywiki-format-reference): Add and use with above function. + (hywiki-insert-reference): Add. + (hywiki-word-is-p): Use 'string-match-p' instead of 'string-match'. + (link-to-wikiword): Add this actype. + (hywiki-insert-link): Update to search for section links to insert + when 'consult' is available. + (require 'hui): Change to an 'eval-when-compile' so does not + trigger recursive requirements during normal loading. + hui-mini.el (hui:menu-hywiki): Unify with "hui-menu.el". Replace use of + 'hywiki-add-path-link' with 'hywiki-insert-link'. + +2026-02-17 Bob Weiner + +* hasht.el (hash-make): Fix to trigger an error if key is not given as a string. + +2026-02-16 Bob Weiner + +* hibtypes.el (markdown-follow-inline-link-p): Fix to handle URL arguments sent + when point is on the link title and not an included URL (so check if on + a URL fails). + +* README.md: Add Hyperbole Articles section. + 2026-02-15 Bob Weiner * hywiki.el (hywiki-word-highlight-in-current-buffer): Fix bug that added diff --git a/README.md b/README.md index 50d10248..2124a699 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,30 @@ otherwise, skip to the next section. - [Linking personal info with implicit buttons](https://youtu.be/TQ_fG7b1iHI) +## Articles + + - [HyWiki: My Favorite Part of Hyperbole](https://kirankp.com/blog/gnu-hyperbole/) + + - [Hyperbole VisionQuest Part 1](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes.org) + + - [Hyperbole VisionQuest Part 2](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes2.org) + + - [A Taste of Hyperbole](https://www.reddit.com/r/emacs/comments/1kty4mb/a_taste_of_hyperbole_automatically_linking_to_org/) + + - [My Understanding of GNU Hyperbole](https://www.reddit.com/r/emacs/comments/nirwpk/my_understanding_of_gnu_hyperbole/) + + - [What does GNU Hyperbole do?](https://tilde.town/~ramin_hal9001/articles/intro-to-hyperbole.html) + + - [John Wiegley - The Philosophy Behind Hyperbole](https://mail.gnu.org/archive/html/hyperbole-users/2019-01/msg00037.html) + + - [Daily ways GNU Hyperbole helps me stay in flow and reduces cognitive load](https://www.reddit.com/r/emacs/comments/jk3cn0/daily_ways_gnu_hyperbole_helps_me_stay_in_flow/) + + - [Doing a Research Project and using GNU Hyperbole's Integrated Features](https://www.reddit.com/r/emacs/comments/1g2184d/doing_a_research_project_and_using_gnu_hyperboles/) + + - [AI-generated Hyperbole Architectural Documentation](https://deepwiki.com/rswgnu/hyperbole) + + - [Hypermedia and Hyperbole](https://www.mgmarlow.com/words/2023-10-26-hyperbole/) + ## Summary `GNU Hyperbole` (pronounced Ga-new Hi-per-bo-lee), or just `Hyperbole`, is diff --git a/hasht.el b/hasht.el index b20b0a05..4ad4af71 100644 --- a/hasht.el +++ b/hasht.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 16-Mar-90 at 03:38:48 -;; Last-Mod: 30-Dec-25 at 14:42:14 by Mats Lidell +;; Last-Mod: 17-Feb-26 at 22:42:00 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -159,16 +159,20 @@ merge all the values for a given instead." key value sym) (if reverse (mapc (lambda (cns) - (when (consp cns) - (setq key (car cns) value (cdr cns))) - (when (setq sym (intern key)) - (puthash sym value hash-table))) + (if (consp cns) + (setq key (car cns) value (cdr cns)) + (setq key nil value nil)) + (if (and (stringp key) (setq sym (intern key))) + (puthash sym value hash-table) + (error "(hash-make): 'key' must be a string, not %S" key))) initializer) (mapc (lambda (cns) - (when (consp cns) - (setq key (cdr cns) value (car cns))) - (when (setq sym (intern key)) - (puthash sym value hash-table))) + (if (consp cns) + (setq key (cdr cns) value (car cns)) + (setq key nil value nil)) + (if (and (stringp key) (setq sym (intern key))) + (puthash sym value hash-table) + (error "(hash-make): 'key' must be a string, not %S" key))) initializer)) hash-table)))) diff --git a/hbut.el b/hbut.el index 96c53c23..850fe36b 100644 --- a/hbut.el +++ b/hbut.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 18-Sep-91 at 02:57:09 -;; Last-Mod: 30-Dec-25 at 23:55:08 by Mats Lidell +;; Last-Mod: 18-Feb-26 at 23:49:33 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -2722,6 +2722,11 @@ Summary of operations based on inputs (name arg from \\='hbut:current attrs): (insert "<" arg1 ">")))) ('actypes::link-to-org-id (insert (format "\"id:%s\"" arg1))) ('actypes::link-to-rfc (insert (format "rfc%d" arg1))) + ('actypes::link-to-wikiword (insert (if (and (stringp arg1) + (string-match-p "\\s-" arg1)) + ;; Double-quote when has a space + (format "\"%s\"" arg1) + arg1))) ('man (insert arg1)) ('actypes::man-show (insert arg1)) ('actypes::link-to-file-line (insert (format "\"%s:%d\"" diff --git a/hibtypes.el b/hibtypes.el index 3c465fa4..61aa356a 100644 --- a/hibtypes.el +++ b/hibtypes.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 19-Sep-91 at 20:45:31 -;; Last-Mod: 2-Feb-26 at 18:22:19 by Bob Weiner +;; Last-Mod: 16-Feb-26 at 18:46:40 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -527,15 +527,17 @@ Return t if jump and nil otherwise." "If on an inline link, jump to its referent if it is absolute and return non-nil. Absolute means not relative within the file. Otherwise, if an internal link, move back to OPOINT and return nil." - ;; Caller already checked not on a URL (handled elsewhere). (let ((path (markdown-link-url))) (goto-char opoint) (when (markdown-link-p) (ibut:label-set (match-string-no-properties 0) (match-beginning 0) (match-end 0)) - (if path - (hact 'link-to-file path) - (hpath:display-buffer (current-buffer)) - (hact 'markdown-follow-link-at-point))))) + (cond ((hpath:url-p path) + (hact 'www-url path)) + (path + (hact 'link-to-file path)) + (t + (hpath:display-buffer (current-buffer)) + (hact 'markdown-follow-link-at-point)))))) (defib markdown-internal-link () "Display any in-file Markdown link referent at point. diff --git a/hsys-consult.el b/hsys-consult.el index 45340ccc..13d11296 100644 --- a/hsys-consult.el +++ b/hsys-consult.el @@ -2,7 +2,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 4-Jul-24 at 09:57:18 -;; Last-Mod: 30-Dec-25 at 14:42:23 by Mats Lidell +;; Last-Mod: 19-Feb-26 at 00:52:16 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -43,8 +43,10 @@ (declare-function hsys-org-directory-at-tags-p "hsys-org") (declare-function hsys-org-at-tags-p "hsys-org") +(declare-function consult--async-command "ext:consult") (declare-function consult--grep "ext:consult") (declare-function consult--grep-make-builder "ext:consult") +(declare-function consult--lookup-member "ext:consult") (declare-function consult--read "ext:consult") (declare-function consult--ripgrep-make-builder "ext:consult") (declare-function consult-grep "ext:consult") diff --git a/hui-mini.el b/hui-mini.el index 182aaf4f..139cb8a7 100644 --- a/hui-mini.el +++ b/hui-mini.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 15-Oct-91 at 20:13:17 -;; Last-Mod: 1-Jan-26 at 18:18:17 by Mats Lidell +;; Last-Mod: 19-Feb-26 at 00:16:53 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -1068,8 +1068,8 @@ support underlined faces as well." "Report on a HyWikiWord's attributes or HyWikiWords in general.") '("Info" (id-info "(hyperbole)HyWiki") "Display Hyperbole manual section on HyWiki.") - '("Link" hywiki-add-path-link - "Prompt for and add a HyWikiWord that links to a path and possible position.") + '("Link" hywiki-insert-link + "Prompt for and insert at point a HyWikiWord#section reference.") '("ModeSet/" (menu . cust-hywiki-mode) "Set hywiki-mode state to determine where HyWikiWord references are recognized.") '("Org-M-RET/" (menu . cust-org) diff --git a/hui.el b/hui.el index 9d90f070..f818c7ed 100644 --- a/hui.el +++ b/hui.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 19-Sep-91 at 21:42:03 -;; Last-Mod: 31-Dec-25 at 16:02:19 by Mats Lidell +;; Last-Mod: 19-Feb-26 at 00:29:40 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -27,6 +27,7 @@ (require 'hmail) (require 'hbut) (eval-when-compile (require 'hactypes)) +(eval-when-compile (require 'hywiki)) ;; Because 'hyperbole' requires this too ;;; ************************************************************************ ;;; Public declarations @@ -2010,6 +2011,7 @@ possible types. Referent Context Possible Link Type Returned ---------------------------------------------------- +HyWikiWord Reference link-to-wikiword Org Roam or Org Id link-to-org-id Global Button link-to-gbut Explicit Button link-to-ebut @@ -2034,7 +2036,9 @@ Buffer without File link-to-buffer-tmp" hbut-sym lbl-key) (prog1 (delq nil - (list (cond ((and (featurep 'org-id) + (list (cond ((let ((ref (hywiki-referent-exists-p))) + (list 'link-to-wikiword ref))) + ((and (featurep 'org-id) (cond ((save-excursion (beginning-of-line) (when (looking-at "[ \t]*:\\(CUSTOM_\\)?ID:[ \t]+\\([^ \t\r\n\f]+\\)") diff --git a/hywiki.el b/hywiki.el index 62df7997..2b7bacaa 100644 --- a/hywiki.el +++ b/hywiki.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 21-Apr-24 at 22:41:13 -;; Last-Mod: 15-Feb-26 at 20:39:33 by Bob Weiner +;; Last-Mod: 19-Feb-26 at 00:57:14 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -38,7 +38,7 @@ ;; - non-special text buffers, when `hywiki-mode' is enabled; ;; - comments and strings in programming buffers, when ;; `hywiki-mode' is enabled. -;; +;; ;; As HyWikiWords are typed, highlighting occurs after a trailing ;; whitespace or punctuation character is added, or when it is ;; surrounded by a matching pair of characters such as curly braces @@ -105,7 +105,7 @@ ;; properties with Org's publishing framework, so when in a HyWiki ;; page, you can use the standard {C-c C-e P p} current project publish ;; command. -;; +;; ;; There are a few publishing settings you can customize prior to ;; loading Hyperbole's HyWiki code. ;; @@ -144,7 +144,7 @@ (require 'hpath) (require 'hproperty) (require 'hsys-consult) -(require 'hui) ;; For `hui:actype' +(eval-when-compile (require 'hui)) ;; For `hui:actype' (require 'hui-mini) ;; For `hui:menu-act' (require 'hypb) ;; Requires `seq' (require 'outline) ;; For `outline-mode-syntax-table' @@ -223,7 +223,7 @@ See `current-time' function for the mod time format.") (defvar hywiki--pages-directory nil) (defvar hywiki--referent-alist nil "HyWiki alist generated from `hywiki--referent-hasht' for storage in cache. -Each element is of the form: (wikiword . (referent-type . referent-value)).") +Each element is of the form: (\"wikiword\" . (referent-type . referent-value)).") (defvar hywiki--referent-hasht nil "HyWiki hash table for fast WikiWord referent lookup.") @@ -1484,8 +1484,7 @@ exists." prompt-flag))) (defun hywiki-completion-at-point () - "Complete HyWiki references. -Ensures that selecting a completion replaces only the text after the '#'." + "Complete HyWiki references, either the HyWikiWord or the #section." (let ((ref-start-end (and (hywiki-active-in-current-buffer-p) (not (hywiki-non-hook-context-p)) (hywiki-word-at t t)))) @@ -1498,7 +1497,7 @@ Ensures that selecting a completion replaces only the text after the '#'." ;; Extract the WikiWord before the '#' (word (hywiki-word-from-reference ref))) (save-excursion - ;; 1. Look for the '#' delimiter on the current line + ;; CASE 1. Look for the '#' delimiter on the current line (if (re-search-backward "#" start t) (let ((hash-pos (point)) (page (expand-file-name (concat word ".org") hywiki-directory))) @@ -1648,6 +1647,43 @@ nil, else return \\='(page . \"\")." (and (or at-tag-flag (hsys-org-at-tags-p)) (or (hywiki-in-page-p) (string-prefix-p "*HyWiki Tags*" (buffer-name))))) +(defun hywiki-consult-file-and-line () + "Return a list of the file and line selected by consult or nil. +Use `hywiki-insert-reference' with the result of this function to insert a +double-quoted HyWikiWord reference at point." + (interactive) + (let* ((dir (expand-file-name hywiki-directory)) + (manual-builder + (lambda (input) + (let* (;; Define the regex inside the builder so it's always in scope + (headline-pattern (concat "^\\* .*" input)) + ;; Ensure all arguments are evaluated as strings + (args (list "rg" + "--null" + "--line-buffered" + "--color=never" + "--with-filename" + "--line-number" + "--smart-case" + "-g" "*.org" + "-e" headline-pattern + dir))) + (cons args dir)))) + (selected (consult--read + (consult--async-command manual-builder) + :prompt "HyWiki Headline: " + :require-match t + :lookup #'consult--lookup-member + :category 'consult-grep))) + + (when (stringp selected) + (if (string-match "\\`\\([^\0]+\\)\0\\([0-9]+\\):\\(.*\\)" selected) + (let ((file (match-string 1 selected)) + (line (match-string 3 selected))) + (list file line)) + (message "(hwiki-consult-file-and-line): Parse error on: %s" selected) + nil)))) + ;;;###autoload (defun hywiki-consult-grep (&optional regexp max-matches path-list prompt) "Interactively search with a consult package grep command. @@ -2045,8 +2081,51 @@ This includes the delimiters: (), {}, <>, [] and \"\" (double quotes)." (defun hywiki-insert-link () "Insert at point a link to a HyWiki page." (interactive "*") - (insert (hywiki-word-read "Link to HyWiki page: ")) - (hywiki-maybe-highlight-reference)) + (let ((ref (if (featurep 'consult) + (hywiki-format-reference (hywiki-consult-file-and-line)) + ;; Without consult, can only complete to a WikiWord + ;; without a section + (hywiki-word-read "Link to HyWiki page: ")))) + (when ref + (insert ref) + (skip-chars-backward "\"") + (goto-char (1- (point))) + (hywiki-maybe-highlight-reference)))) + +;;;###autoload +(defun hywiki-format-reference (page-and-line) + "Return a HyWikiWord#section reference, PAGE-AND-LINE, from `consult-grep'. +Add double quotes if the section contains any whitespace after trimming. + +Return t if PAGE-AND-LINE is a valid list, else nil. If the page name +therein is invalid, trigger an error." + (when (and page-and-line (listp page-and-line)) + (cl-destructuring-bind (page line) + page-and-line + (setq page (file-name-base page)) + (when (not (string-match-p hywiki-word-regexp page)) + (error "(hywiki-format-reference): Invalid HyWiki page name - \"%s\"" + page)) + ;; Drop '* ' prefix + (setq line (string-trim line "[ \t\n\r]*\\**[ \t\n\r]+")) + (format (if (string-match-p "\\s-" line) + "\"%s#%s\"" + "%s#%s") + page + line)))) + +(defun hywiki-insert-reference (page-and-line) + "Insert a HyWikiWord#section reference, PAGE-AND-LINE, from `consult-grep'. +Add double quotes if the section contains any whitespace after trimming. + +Return t if PAGE-AND-LINE is a valid list, else nil. If the page name +therein is invalid, trigger an error." + (let ((ref (hywiki-format-reference page-and-line))) + (when ref + (insert ref) + (skip-chars-backward "\"") + (goto-char (1- (point))) + t))) (defun hywiki-maybe-dehighlight-balanced-pairs () "Before or after a balanced delimiter, dehighlight HyWikiWords within. @@ -3232,36 +3311,37 @@ Customize this directory with: ;; spaces replaced with dashes, made unique when necessary. (org-publish-project "hywiki" all-pages-flag)) -(defun hywiki-referent-exists-p (&optional word start end) - "Return the HyWikiWord at point or optional HyWiki WORD, if has a referent. +(defun hywiki-referent-exists-p (&optional ref start end) + "Return the HyWiki reference at point or optional REF, if has a referent. If no such referent exists, return nil. -The HyWikiWord may be of the form: +The HyWikiWord reference may be of the form: 1. HyWikiWord#section with an optional #section. - 2. If WORD is the symbol, :range, and there is a HyWikiWord at point - with an existing referent, return the tuple of values: \='( - ) instead of the word; otherwise, return the - tuple \='(nil nil nil). - -When using the word at point, a call to `hywiki-active-in-current-buffer-p' -at point must return non-nil or this function will return nil." - (let ((save-input-word word)) - (when (stringp word) - (setq word (hywiki-strip-org-link word))) - (if (or (stringp word) - (setq word (hywiki-word-get-range))) - (unless (hywiki-get-referent (if (stringp word) word (nth 0 word))) - (setq word nil)) - (setq word nil)) - (when (and (listp word) (= (length word) 3)) - (setq start (nth 1 word) - end (nth 2 word) - ;; `word' must be set last so list version can be referenced + 2. If REF is the symbol, :range, and there is a HyWikiWord at point + with an existing referent, return the tuple of values: \='( + ) instead of the reference alone; otherwise, + return the tuple \='(nil nil nil). + +When using the reference at point, a call to +`hywiki-active-in-current-buffer-p' at point must return non-nil or this +function will return nil." + (let ((save-input-word ref)) + (when (stringp ref) + (setq ref (hywiki-strip-org-link ref))) + (if (or (stringp ref) + (setq ref (hywiki-word-get-range))) + (unless (hywiki-get-referent (if (stringp ref) ref (nth 0 ref))) + (setq ref nil)) + (setq ref nil)) + (when (and (listp ref) (= (length ref) 3)) + (setq start (nth 1 ref) + end (nth 2 ref) + ;; `ref' must be set last so list version can be referenced ;; first above - word (nth 0 word))) + ref (nth 0 ref))) (if (eq save-input-word :range) - (list word start end) - word))) + (list ref start end) + ref))) (defun hywiki-section-to-headline-reference () "Replace file#section dashes with spaces to match to an Org headline. @@ -3735,17 +3815,16 @@ Return nil if WORD is a prefixed, typed hy:HyWikiWord, since these are handled by the Org mode link handler." (and (stringp word) (not (string-empty-p word)) (let (case-fold-search) - (and (or (string-match hywiki-word-with-optional-suffix-exact-regexp word) + (and (or (string-match-p hywiki-word-with-optional-suffix-exact-regexp word) ;; For now this next version allows spaces and tabs in ;; the suffix part (eq 0 (string-match hywiki-word-with-optional-suffix-exact-regexp word))) - (save-match-data - ;; If has a #section, ensure there are no invalid chars - (if (string-match "#" word) - (string-match "#[^][#()<>{}\"\n\r\f]+\\'" word) - t)))))) + ;; If has a #section, ensure there are no invalid chars + (if (string-match-p "#" word) + (string-match-p "#[^][#()<>{}\"\n\r\f]+\\'" word) + t))))) (defun hywiki-word-read (&optional prompt) "Prompt with completion for and return an existing HyWikiWord. @@ -3798,7 +3877,7 @@ occurs with one of these hooks, the problematic hook is removed." (hywiki-word-highlight-buffers (set:difference (hywiki-get-buffers hywiki-from-mode) (hywiki-get-buffers hywiki-to-mode)))) - (t + (t (error "(hywiki-word-set-auto-highlighting): Inputs must be nil, :pages or :all, not '%s' and '%s'" hywiki-from-mode hywiki-to-mode)))) @@ -3884,6 +3963,16 @@ completion or no completion xandidates are returned." (when (called-interactively-p 'interactive) (message "HyWikiWord auto-highlighting disabled"))) +(defact link-to-wikiword (reference) + "Display the HyWikiword referent matching WikiWord#section REFERENCE." + (interactive (list + (if (featurep 'consult) + (hywiki-format-reference (hywiki-consult-file-and-line)) + ;; Without consult, can only complete to a WikiWord + ;; without a section + (hywiki-word-read "Link to HyWiki page: ")))) + (hywiki-find-referent reference)) + ;;; ************************************************************************ ;;; Private functions ;;; ************************************************************************ diff --git a/man/hyperbole.texi b/man/hyperbole.texi index 6d630542..ef83bcf8 100644 --- a/man/hyperbole.texi +++ b/man/hyperbole.texi @@ -7,7 +7,7 @@ @c Author: Bob Weiner @c @c Orig-Date: 6-Nov-91 at 11:18:03 -@c Last-Mod: 7-Feb-26 at 10:10:33 by Bob Weiner +@c Last-Mod: 18-Feb-26 at 23:43:55 by Bob Weiner @c %**start of header (This is for running Texinfo on a region.) @setfilename hyperbole.info @@ -30,7 +30,7 @@ @set txicodequoteundirected @set txicodequotebacktick -@set UPDATED February 7, 2026 +@set UPDATED February 18, 2026 @set UPDATED-MONTH February 2026 @set EDITION 9.0.2pre @set VERSION 9.0.2pre @@ -171,7 +171,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 Edition 9.0.2pre
-Printed February 7, 2026.
+Printed February 18, 2026.
 
   Published by the Free Software Foundation, Inc.
   Author:    Bob Weiner
@@ -213,7 +213,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 @example
 Edition 9.0.2pre
-February 7, 2026 @c AUTO-REPLACE-ON-SAVE
+February 18, 2026 @c AUTO-REPLACE-ON-SAVE
 
 
   Published by the Free Software Foundation, Inc.
@@ -3887,6 +3887,7 @@ upon the referent context in which the Action Key is released.
 @example
 Referent Context         Link Type
 ----------------------------------------------------
+HyWikiWord Reference     link-to-wikiword
 Org Roam or Org Id       link-to-org-id
 Global Button            link-to-gbut
 Explicit Button          link-to-ebut

From bac4fd78fbbae8963b1a6433e7eabdcf6fe17dba Mon Sep 17 00:00:00 2001
From: bw 
Date: Thu, 19 Feb 2026 01:16:43 -0500
Subject: [PATCH 2/4] hui.el - Eliminate recursive require with 'hywiki

---
 README.toc.md     | 31 +++++++++++++++++++++++++++----
 hui.el            |  5 +++--
 hywiki.el         |  4 ++--
 kotl/kotl-mode.el |  8 +++++---
 4 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/README.toc.md b/README.toc.md
index 18cb88f5..0231bc28 100644
--- a/README.toc.md
+++ b/README.toc.md
@@ -7,11 +7,10 @@
  send us a thank you or a testimonial describing your usage if you like
  Hyperbole to [rsw@gnu.org](mailto:rsw@gnu.org)].
 
-
-
 - [GNU Hyperbole 9.0.2pre - The Everyday Hypertextual Information Manager](#gnu-hyperbole-902pre---the-everyday-hypertextual-information-manager)
   - [Reference Manual](#reference-manual)
   - [Videos](#videos)
+  - [Articles](#articles)
   - [Summary](#summary)
   - [Installation](#installation)
   - [Invocation](#invocation)
@@ -26,8 +25,6 @@
   - [User Quotes](#user-quotes)
   - [Why was Hyperbole developed?](#why-was-hyperbole-developed)
 
-
-
 ![Hyperbole screenshot of the Koutliner, DEMO file and HyRolo](man/im/hyperbole-cv.png)
 
 ## Reference Manual
@@ -74,6 +71,30 @@ otherwise, skip to the next section.
 
    - [Linking personal info with implicit buttons](https://youtu.be/TQ_fG7b1iHI)
 
+## Articles
+
+   - [HyWiki: My Favorite Part of Hyperbole](https://kirankp.com/blog/gnu-hyperbole/)
+
+   - [Hyperbole VisionQuest Part 1](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes.org)
+
+   - [Hyperbole VisionQuest Part 2](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes2.org)
+
+   - [A Taste of Hyperbole](https://www.reddit.com/r/emacs/comments/1kty4mb/a_taste_of_hyperbole_automatically_linking_to_org/)
+
+   - [My Understanding of GNU Hyperbole](https://www.reddit.com/r/emacs/comments/nirwpk/my_understanding_of_gnu_hyperbole/)
+
+   - [What does GNU Hyperbole do?](https://tilde.town/~ramin_hal9001/articles/intro-to-hyperbole.html)
+
+   - [John Wiegley - The Philosophy Behind Hyperbole](https://mail.gnu.org/archive/html/hyperbole-users/2019-01/msg00037.html)
+
+   - [Daily ways GNU Hyperbole helps me stay in flow and reduces cognitive load](https://www.reddit.com/r/emacs/comments/jk3cn0/daily_ways_gnu_hyperbole_helps_me_stay_in_flow/)
+
+   - [Doing a Research Project and using GNU Hyperbole's Integrated Features](https://www.reddit.com/r/emacs/comments/1g2184d/doing_a_research_project_and_using_gnu_hyperboles/)
+
+   - [AI-generated Hyperbole Architectural Documentation](https://deepwiki.com/rswgnu/hyperbole)
+
+   - [Hypermedia and Hyperbole](https://www.mgmarlow.com/words/2023-10-26-hyperbole/)
+
 ## Summary
 
 `GNU Hyperbole` (pronounced Ga-new Hi-per-bo-lee), or just `Hyperbole`, is
@@ -255,6 +276,8 @@ window control menu if it was not already bound prior to Hyperbole's
 initialization.  A long video demonstrating many of HyControl's
 features is available at https://youtu.be/M3-aMh1ccJk.
 
+## Hyperbole Manual
+
 The above are the best interactive ways to learn about Hyperbole.
 Hyperbole also includes the Hyperbole Manual, a full reference manual,
 not a simple introduction.  It is included in the "man/" subdirectory
diff --git a/hui.el b/hui.el
index f818c7ed..3a2f5571 100644
--- a/hui.el
+++ b/hui.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    19-Sep-91 at 21:42:03
-;; Last-Mod:     19-Feb-26 at 00:29:40 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 01:08:15 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -27,7 +27,6 @@
 (require 'hmail)
 (require 'hbut)
 (eval-when-compile (require 'hactypes))
-(eval-when-compile (require 'hywiki)) ;; Because 'hyperbole' requires this too
 
 ;;; ************************************************************************
 ;;; Public declarations
@@ -38,8 +37,10 @@
 (defvar completion-to-accept)           ; "completion.el"
 (defvar hyperbole-mode-map)             ; "hyperbole.el"
 
+(declare-function actypes::link-to-wikiword "hywiki")
 (declare-function bookmark-bmenu-bookmark "bookmark")
 (declare-function hui:menu-choose "hui-mini")
+(declare-function hywiki-referent-exists-p "hywiki")
 (declare-function kcell-view:absolute-reference "kotl/kview")
 (declare-function kcell-view:idstamp "kotl/kview")
 (declare-function klink:absolute "kotl/klink")
diff --git a/hywiki.el b/hywiki.el
index 2b7bacaa..0d7e5cb3 100644
--- a/hywiki.el
+++ b/hywiki.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    21-Apr-24 at 22:41:13
-;; Last-Mod:     19-Feb-26 at 00:57:14 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 01:08:50 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -144,7 +144,7 @@
 (require 'hpath)
 (require 'hproperty)
 (require 'hsys-consult)
-(eval-when-compile (require 'hui)) ;; For `hui:actype'
+(require 'hui)        ;; For `hui:actype'
 (require 'hui-mini)   ;; For `hui:menu-act'
 (require 'hypb)       ;; Requires `seq'
 (require 'outline)    ;; For `outline-mode-syntax-table'
diff --git a/kotl/kotl-mode.el b/kotl/kotl-mode.el
index 76f4b08d..b997d739 100644
--- a/kotl/kotl-mode.el
+++ b/kotl/kotl-mode.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    6/30/93
-;; Last-Mod:     19-Jan-26 at 22:34:04 by Mats Lidell
+;; Last-Mod:     19-Feb-26 at 01:12:25 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -2491,8 +2491,10 @@ Optional prefix arg RELATIVE-LEVEL means one of the following:
 
  1. when = 0, add as the parent's first child cell (first cell in list);
  2. when < 0, add that number of cells as preceding siblings;
- 3. when \\='(4) (universal arg, \\`C-u'), add as the first child of the current cell;
- 4. when > 0 or nil (meaning 1), add that number of cells as following siblings."
+ 3. when \\='(4) (universal arg, \\`C-u'), add as the first child of the
+    current cell;
+ 4. when > 0 or nil (meaning 1), add that number of cells as following
+    siblings."
   (interactive "*P")
   (unless (or (integerp relative-level) (listp relative-level) )
     (error "(kotl-mode:add-cell): `relative-level' must be an integer or a list of integers, not '%s'" relative-level))

From 9d359d983166ff9fba2f4acfd50a204c8f273454 Mon Sep 17 00:00:00 2001
From: bw 
Date: Thu, 19 Feb 2026 19:32:39 -0500
Subject: [PATCH 3/4] (hywiki-word-is-p): Revert to using all 'string-match'
 calls

This fixes test match-string references.  Also fix a number of
functions to allow only page name wikiwords.

hywiki-add-page - Fix a number of issues where non-page WikiWords
were allowed.
---
 ChangeLog | 16 ++++++++++-
 hywiki.el | 82 +++++++++++++++++++++++++++++++++++--------------------
 2 files changed, 68 insertions(+), 30 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 2725b473..597f9e41 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,18 @@
-2026-02-18  Bob Weiner  
+2026-02-19  Bob Weiner  
+
+* hywiki.el (hywiki-read-page-reference): Add and use in 'interactive' calls of
+    several functions.
+            (hywiki-word-is-p): Revert to using all 'string-match' calls
+    to prevent any regressions.
+            (hywiki-page-read): Add to read only existing HyWiki page names.
+    Not all existing WikiWords have page names, as there are other referent types.
+            (hywiki-consult-file-and-line): Rename to `hywiki-consult-page-and-line'.
+	    (hywiki-page-exists-p): Add.
+	    (hywiki-format-reference): Fix to accept only existing page references.
+	    (hywiki-read-reference): Delete, use 'hywiki-word-read' instead.
+	    (hywiki-add-page): Fix a number of issues where non-page WikiWords were
+    allowed.
+            (hywiki-page-read-new): Prevent selection of non-page HyWikiWords.
 
 * hui.el (require 'hywiki):
          (hui:link-possible-types): Add 'link-to-wikiword' clause.
diff --git a/hywiki.el b/hywiki.el
index dc04a2aa..b48e1caf 100644
--- a/hywiki.el
+++ b/hywiki.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    21-Apr-24 at 22:41:13
-;; Last-Mod:     19-Feb-26 at 01:08:50 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 19:29:13 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -945,9 +945,10 @@ Function used to display is \"hywiki-display-\"."
 (defun hywiki-display-referent (&optional wikiword prompt-flag)
   "Display HyWiki WIKIWORD referent or a regular file with WIKIWORD nil.
 Return the WIKIWORD's referent if successfully found or nil otherwise.
-The referent is a cons of ( . ).
+
 For further details, see documentation for `hywiki-find-referent'.
 After successfully finding a referent, run `hywiki-display-referent-hook'."
+  (interactive (list (hywiki-read-page-reference)))
   (let ((in-page-flag (null wikiword))
 	(in-hywiki-directory-flag (hywiki-in-page-p)))
     (if (or (stringp wikiword) in-hywiki-directory-flag)
@@ -1399,16 +1400,15 @@ After successfully adding a page, run `hywiki-add-page-hook'.
 
 Use `hywiki-get-referent' to determine whether a HyWiki page exists."
   (interactive (list (or (hywiki-word-at)
-			 (hywiki-word-read-new "Add/Edit HyWiki page: "))
+			 (hywiki-page-read-new "Add/Edit HyWiki page: "))
 		     current-prefix-arg))
   (if (hywiki-word-is-p page-name)
       (when (or noninteractive
 		(not (hash-empty-p (hywiki-get-referent-hasht)))
 		(hyperb:stack-frame '(ert-run-test))
 		(y-or-n-p (concat "Create new HyWiki page `" page-name "'? ")))
-	(when (match-string-no-properties 2 page-name)
-	  ;; Remove any #section suffix in PAGE-NAME.
-	  (setq page-name (match-string-no-properties 1 page-name)))
+	;; Remove any #section suffix in PAGE-NAME.
+	(setq page-name (hywiki-get-singular-wikiword page-name))
 
 	(let* ((page-file (hywiki-get-page-file page-name))
 	       (page-file-readable (file-readable-p page-file))
@@ -1647,7 +1647,7 @@ nil, else return \\='(page . \"\")."
   (and (or at-tag-flag (hsys-org-at-tags-p))
        (or (hywiki-in-page-p) (string-prefix-p "*HyWiki Tags*" (buffer-name)))))
 
-(defun hywiki-consult-file-and-line ()
+(defun hywiki-consult-page-and-line ()
   "Return a list of the file and line selected by consult or nil.
 Use `hywiki-insert-reference' with the result of this function to insert a
 double-quoted HyWikiWord reference at point."
@@ -2077,15 +2077,20 @@ This includes the delimiters: (), {}, <>, [] and \"\" (double quotes)."
 	    result
 	  (list nil nil))))))
 
+(defun hywiki-read-page-reference ()
+  "With consult package loaded, read a \"file^@line\" string, else a page name."
+  (interactive)
+  (if (featurep 'consult)
+      (hywiki-format-reference (hywiki-consult-page-and-line))
+    ;; Without consult, can only complete to a HyWiki page
+    ;; without a section
+    (hywiki-page-read "Link to HyWiki page: ")))
+
 ;;;###autoload
 (defun hywiki-insert-link ()
-  "Insert at point a link to a HyWiki page."
+  "Insert at point a link to a HyWiki page#section."
   (interactive "*")
-  (let ((ref (if (featurep 'consult)
-	         (hywiki-format-reference (hywiki-consult-file-and-line))
-	       ;; Without consult, can only complete to a WikiWord
-	       ;; without a section
-               (hywiki-word-read "Link to HyWiki page: "))))
+  (let ((ref (hywiki-read-page-reference)))
     (when ref
       (insert ref)
       (skip-chars-backward "\"")
@@ -2094,7 +2099,8 @@ This includes the delimiters: (), {}, <>, [] and \"\" (double quotes)."
 
 ;;;###autoload
 (defun hywiki-format-reference (page-and-line)
-  "Return a HyWikiWord#section reference, PAGE-AND-LINE, from `consult-grep'.
+  "Return a HyWikiWord#section reference from PAGE-AND-LINE.
+Call `hywiki-consult-page-and-line' to generate PAGE-AND-LINE.
 Add double quotes if the section contains any whitespace after trimming.
 
 Return t if PAGE-AND-LINE is a valid list, else nil.  If the page name
@@ -2103,7 +2109,8 @@ therein is invalid, trigger an error."
     (cl-destructuring-bind (page line)
 	page-and-line
       (setq page (file-name-base page))
-      (when (not (string-match-p hywiki-word-regexp page))
+      (unless (and (string-match-p hywiki-word-regexp page)
+                   (hywiki-page-exists-p page))
 	(error "(hywiki-format-reference): Invalid HyWiki page name - \"%s\""
 	       page))
       ;; Drop '* ' prefix
@@ -2115,7 +2122,7 @@ therein is invalid, trigger an error."
 	      line))))
 
 (defun hywiki-insert-reference (page-and-line)
-  "Insert a HyWikiWord#section reference, PAGE-AND-LINE, from `consult-grep'.
+  "Insert a HyWiki page#section reference from PAGE-AND-LINE.
 Add double quotes if the section contains any whitespace after trimming.
 
 Return t if PAGE-AND-LINE is a valid list, else nil.  If the page name
@@ -3816,7 +3823,7 @@ Return nil if WORD is a prefixed, typed hy:HyWikiWord, since
 these are handled by the Org mode link handler."
   (and (stringp word) (not (string-empty-p word))
        (let (case-fold-search)
-	 (and (or (string-match-p hywiki-word-with-optional-suffix-exact-regexp word)
+	 (and (or (string-match hywiki-word-with-optional-suffix-exact-regexp word)
 		  ;; For now this next version allows spaces and tabs in
 		  ;; the suffix part
 		  (eq 0 (string-match
@@ -3824,7 +3831,7 @@ these are handled by the Org mode link handler."
 			 word)))
 	      ;; If has a #section, ensure there are no invalid chars
 	      (if (string-match-p "#" word)
-		  (string-match-p "#[^][#()<>{}\"\n\r\f]+\\'" word)
+		  (string-match "#[^][#()<>{}\"\n\r\f]+\\'" word)
 		t)))))
 
 (defun hywiki-word-read (&optional prompt)
@@ -3843,13 +3850,35 @@ If point is on one, press RET immediately to use that one."
 		     (hywiki-get-referent-hasht)
 		     nil nil nil nil (hywiki-word-at-point))))
 
-(defun hywiki-page-read-new (&optional prompt)
-  "Prompt with completion for and return an existing/new HyWikiWord with a page.
+(defun hywiki-page-exists-p (word)
+  "Return HyWiki WORD iff it is an existing page reference."
+  (when (eq (car (hywiki-get-referent word)) 'page)
+    word))
+
+(defun hywiki-page-read (&optional prompt)
+  "Prompt with completion for and return an existing HyWiki page name.
 If point is on one, press RET immediately to use that one."
-  (let ((completion-ignore-case t))
-    (completing-read (if (stringp prompt) prompt "HyWikiWord page: ")
+  (let* ((completion-ignore-case t)
+         (wikiword (hywiki-word-at-point))
+         (page (hywiki-page-exists-p wikiword)))
+    (completing-read (if (stringp prompt) prompt "HyWiki page: ")
 		     (hywiki-get-page-list)
-		     nil nil nil nil (hywiki-word-at-point))))
+		     nil t nil nil (when page wikiword))))
+
+(defun hywiki-page-read-new (&optional prompt)
+  "Prompt with completion for and return an existing/new HyWiki page name.
+If point is on one, press RET immediately to use that one."
+  (let ((completion-ignore-case t)
+        page)
+    (while (null page)
+      (setq page (completing-read
+                  (if (stringp prompt) prompt "HyWiki page: ")
+		  (hywiki-get-page-list)
+		  nil nil nil nil (hywiki-word-at-point)))
+      ;; Prevent selection of non-page HyWikiWords
+      (unless (memq (car (hywiki-get-referent page)) '(page nil))
+        (setq page nil)))
+    page))
 
 (defun hywiki-word-set-auto-highlighting (hywiki-from-mode hywiki-to-mode)
   "Set HyWikiWord auto-highlighting based on HYWIKI-FROM-MODE HYWIKI-TO-MODE.
@@ -3966,12 +3995,7 @@ completion or no completion xandidates are returned."
 
 (defact link-to-wikiword (reference)
   "Display the HyWikiword referent matching WikiWord#section REFERENCE."
-  (interactive (list
-		(if (featurep 'consult)
-		    (hywiki-format-reference (hywiki-consult-file-and-line))
-		  ;; Without consult, can only complete to a WikiWord
-		  ;; without a section
-                  (hywiki-word-read "Link to HyWiki page: "))))
+  (interactive (list (hywiki-word-read "Link to HyWiki word: ")))
   (hywiki-find-referent reference))
 
 ;;; ************************************************************************

From 447b83c2ca402bd4285bc3d097ed1ed1bc7c37bb Mon Sep 17 00:00:00 2001
From: bw 
Date: Thu, 19 Feb 2026 21:41:59 -0500
Subject: [PATCH 4/4] ibtypes::action - Fix to check for angle bracket
 delimiters.

hui:link-possible-types - Fix to include link-to-wikiword only if
an existing referent is found.
---
 ChangeLog   |   5 ++
 hibtypes.el | 206 ++++++++++++++++++++++++++--------------------------
 hui.el      |   4 +-
 3 files changed, 111 insertions(+), 104 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 597f9e41..4d4c3198 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2026-02-19  Bob Weiner  
 
+* hui.el (hui:link-possible-types): Fix to include link-to-wikiword only if
+    an existing referent is found.
+
+* hibtypes.el (action): Fix to check for angle bracket delimiters.
+
 * hywiki.el (hywiki-read-page-reference): Add and use in 'interactive' calls of
     several functions.
             (hywiki-word-is-p): Revert to using all 'string-match' calls
diff --git a/hibtypes.el b/hibtypes.el
index 61aa356a..1786e178 100644
--- a/hibtypes.el
+++ b/hibtypes.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    19-Sep-91 at 20:45:31
-;; Last-Mod:     16-Feb-26 at 18:46:40 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 21:16:16 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -1608,110 +1608,112 @@ first identifier in the expression must be an Elisp variable,
 action type, function symbol to call or test to execute, i.e.
 '<'actype-or-elisp-symbol arg1 ... argN '>'.  For example,
 ."
-  (let ((hbut:max-len 0)
-	(lbl-key (hattr:get 'hbut:current 'lbl-key))
-	(name (hattr:get 'hbut:current 'name))
+  (let ((lbl-key (hattr:get 'hbut:current 'lbl-key))
 	(start-pos (hattr:get 'hbut:current 'lbl-start))
-	(end-pos  (hattr:get 'hbut:current 'lbl-end))
-	(testing-flag (when (bound-and-true-p ert--running-tests) t))
-        actype actype-sym action args lbl var-flag)
-
-    ;; Continue only if there if there is a button label and one of:
-    ;;  1. `ert--running-tests' is non-nil
-    ;;  2. character after start-delim is not a whitespace character
+	(end-pos  (hattr:get 'hbut:current 'lbl-end)))
     (when (and lbl-key
-	       (or testing-flag
-		   (not (memq (if (char-after (1+ start-pos))
-				       (char-syntax (char-after (1+ start-pos)))
-				     0)
-				   '(?\  ?\>)))))
-      (setq lbl (ibut:key-to-label lbl-key))
-      ;; Handle $ preceding var name in cases where same name is
-      ;; bound as a function symbol
-      (when (string-match "\\`\\$" lbl)
-        (setq var-flag t
-	      lbl (substring lbl 1)))
-      (setq actype (if (string-match-p " " lbl) (car (split-string lbl)) lbl)
-            actype-sym (or (actype:elisp-symbol actype) (intern-soft actype))
-	    ;; Must ignore that (boundp nil) would be t here.
-            actype (and actype-sym
-			(or (fboundp actype-sym) (boundp actype-sym)
-			    (special-form-p actype-sym)
-			    (ert-test-boundp actype-sym))
-			actype-sym))
-      (when actype
-	;; For  buttons, need to double quote each argument so
-	;; 'read' does not change the idstamp 02 to 2.
-	(when (and (memq actype '(hy hynote))
-		   (string-match-p " " lbl))
-	  (setq lbl (replace-regexp-in-string "\"\\(.*\\)\\'" "\\1\""
-					      (combine-and-quote-strings
-					       (split-string lbl) "\" \""))))
-        (setq action (read (concat "(" lbl ")"))
-	      args (cdr action))
-	;; Ensure action uses an fboundp symbol if executing a
-	;; Hyperbole actype.
-	(when (and (car action) (symbolp (car action)))
-	  (setcar action (or (symtable:hyperbole-actype-p (car action))
-			     (car action))))
-	(unless assist-flag
-          (cond ((and (symbolp actype) (fboundp actype)
-		      (string-match "-p\\'" (symbol-name actype)))
-		 ;; Is a function with a boolean result
-		 (setq actype #'display-boolean
-		       args `(',action)))
-		((and (null args) (symbolp actype) (boundp actype)
-		      (or var-flag (not (fboundp actype))))
-		 ;; Is a variable, display its value as the action
-		 (setq args `(,actype)
-		       actype #'display-variable))
-		((and (null args) (symbolp actype) (ert-test-boundp actype))
-		 ;; Is an ert-deftest, display the value from executing it
-		 (setq actype #'display-value
-		       args `((hypb-ert-run-test ,lbl))))
-		(t
-		 ;; All other expressions, display the action result in the minibuffer
-		 (if (string-match "\\b\\(delete\\|kill\\)-region\\'"
-				   (symbol-name actype-sym))
-		     ;; With `delete-region' and `kill-region'
-		     ;; actions, if no args, either use any active
-		     ;; region or when none, use the region of the
-		     ;; action button itself, removing it from the
-		     ;; buffer.  The latter action is largely used
-		     ;; only in internal HyWiki tests.
-		     (progn (setq actype #'display-value)
- 			    (if (= 1 (length action)) ;; No args
-				(if (use-region-p)
-				    ;; Apply function to the active region
-				    (setq args `((,actype-sym (region-beginning) (region-end))))
-				  ;; Apply function to region of the action button itself,
-				  ;; including delimiters
-				  (setq args `((,actype-sym ,start-pos
-							    ,end-pos))))
-			      (setq args `(',action))))
-		   (if testing-flag
-		       ;; Delete action button after activation when
-		       ;; running an ert test or in a string (so can
-		       ;; test this behavior interactively),
-		       (setq actype #'display-value-and-remove-region
-			     args `(,action ,start-pos ,end-pos))
+               (eq (char-after start-pos) ?\<)
+               (eq (char-before end-pos) ?\>))
+      (let ((hbut:max-len 0)
+	    (name (hattr:get 'hbut:current 'name))
+	    (testing-flag (when (bound-and-true-p ert--running-tests) t))
+            actype actype-sym action args lbl var-flag)
+
+        ;; Continue only if there if there is one of:
+        ;;  1. `ert--running-tests' is non-nil
+        ;;  2. character after start-delim is not a whitespace character
+        (when (and (or testing-flag
+		       (not (memq (if (char-after (1+ start-pos))
+				      (char-syntax (char-after (1+ start-pos)))
+				    0)
+				  '(?\  ?\>)))))
+          (setq lbl (ibut:key-to-label lbl-key))
+          ;; Handle $ preceding var name in cases where same name is
+          ;; bound as a function symbol
+          (when (string-match "\\`\\$" lbl)
+            (setq var-flag t
+	          lbl (substring lbl 1)))
+          (setq actype (if (string-match-p " " lbl) (car (split-string lbl)) lbl)
+                actype-sym (or (actype:elisp-symbol actype) (intern-soft actype))
+	        ;; Must ignore that (boundp nil) would be t here.
+                actype (and actype-sym
+			    (or (fboundp actype-sym) (boundp actype-sym)
+			        (special-form-p actype-sym)
+			        (ert-test-boundp actype-sym))
+			    actype-sym))
+          (when actype
+	    ;; For  buttons, need to double quote each argument so
+	    ;; 'read' does not change the idstamp 02 to 2.
+	    (when (and (memq actype '(hy hynote))
+		       (string-match-p " " lbl))
+	      (setq lbl (replace-regexp-in-string "\"\\(.*\\)\\'" "\\1\""
+					          (combine-and-quote-strings
+					           (split-string lbl) "\" \""))))
+            (setq action (read (concat "(" lbl ")"))
+	          args (cdr action))
+	    ;; Ensure action uses an fboundp symbol if executing a
+	    ;; Hyperbole actype.
+	    (when (and (car action) (symbolp (car action)))
+	      (setcar action (or (symtable:hyperbole-actype-p (car action))
+			         (car action))))
+	    (unless assist-flag
+              (cond ((and (symbolp actype) (fboundp actype)
+		          (string-match "-p\\'" (symbol-name actype)))
+		     ;; Is a function with a boolean result
+		     (setq actype #'display-boolean
+		           args `(',action)))
+		    ((and (null args) (symbolp actype) (boundp actype)
+		          (or var-flag (not (fboundp actype))))
+		     ;; Is a variable, display its value as the action
+		     (setq args `(,actype)
+		           actype #'display-variable))
+		    ((and (null args) (symbolp actype) (ert-test-boundp actype))
+		     ;; Is an ert-deftest, display the value from executing it
 		     (setq actype #'display-value
-			   args `(,action)))))))
-
-	;; Create implicit button object and store in symbol hbut:current.
-	(ibut:label-set lbl)
-	(ibut:create :name name :lbl-key lbl-key :lbl-start start-pos
-		     :lbl-end end-pos :categ 'ibtypes::action :actype actype
-		     :args args)
-
-        ;; Necessary so can return a null value, which actype:act cannot.
-        (let ((hrule:action
-	       (if (eq hrule:action #'actype:identity)
-                   #'actype:identity
-                 #'actype:eval)))
-          (if (eq hrule:action #'actype:identity)
-	      `(hact ',actype ,@args)
-            `(hact ',actype ,@(mapcar #'eval args))))))))
+		           args `((hypb-ert-run-test ,lbl))))
+		    (t
+		     ;; All other expressions, display the action result in the minibuffer
+		     (if (string-match "\\b\\(delete\\|kill\\)-region\\'"
+				       (symbol-name actype-sym))
+		         ;; With `delete-region' and `kill-region'
+		         ;; actions, if no args, either use any active
+		         ;; region or when none, use the region of the
+		         ;; action button itself, removing it from the
+		         ;; buffer.  The latter action is largely used
+		         ;; only in internal HyWiki tests.
+		         (progn (setq actype #'display-value)
+ 			        (if (= 1 (length action)) ;; No args
+				    (if (use-region-p)
+				        ;; Apply function to the active region
+				        (setq args `((,actype-sym (region-beginning) (region-end))))
+				      ;; Apply function to region of the action button itself,
+				      ;; including delimiters
+				      (setq args `((,actype-sym ,start-pos
+							        ,end-pos))))
+			          (setq args `(',action))))
+		       (if testing-flag
+		           ;; Delete action button after activation when
+		           ;; running an ert test or in a string (so can
+		           ;; test this behavior interactively),
+		           (setq actype #'display-value-and-remove-region
+			         args `(,action ,start-pos ,end-pos))
+		         (setq actype #'display-value
+			       args `(,action)))))))
+
+	    ;; Create implicit button object and store in symbol hbut:current.
+	    (ibut:label-set lbl)
+	    (ibut:create :name name :lbl-key lbl-key :lbl-start start-pos
+		         :lbl-end end-pos :categ 'ibtypes::action :actype actype
+		         :args args)
+
+            ;; Necessary so can return a null value, which actype:act cannot.
+            (let ((hrule:action
+	           (if (eq hrule:action #'actype:identity)
+                       #'actype:identity
+                     #'actype:eval)))
+              (if (eq hrule:action #'actype:identity)
+	          `(hact ',actype ,@args)
+                `(hact ',actype ,@(mapcar #'eval args))))))))))
 
 (defun action:help (hbut)
   "Display documentation for action button at point.
diff --git a/hui.el b/hui.el
index 3a2f5571..3486e7a8 100644
--- a/hui.el
+++ b/hui.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    19-Sep-91 at 21:42:03
-;; Last-Mod:     19-Feb-26 at 01:08:15 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 21:32:21 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -2038,7 +2038,7 @@ Buffer without File      link-to-buffer-tmp"
 	lbl-key)
     (prog1 (delq nil
 		 (list (cond ((let ((ref (hywiki-referent-exists-p)))
-				(list 'link-to-wikiword ref)))
+				(and ref (list 'link-to-wikiword ref))))
                              ((and (featurep 'org-id)
 				   (cond ((save-excursion
 					    (beginning-of-line)