# Ann: SWI-Prolog 10.1.16

**URL:** <https://swi-prolog.discourse.group/t/ann-swi-prolog-10-1-16/9837>\
**Category:** Releases\
**Created:** [September 25, 2026, 1:01pm UTC](https://swi-prolog.discourse.group/t/ann-swi-prolog-10-1-16/9837 "2026-09-25T13:01:24Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [September 25, 2026, 1:01pm UTC](https://swi-prolog.discourse.group/t/ann-swi-prolog-10-1-16/9837/1 "2026-09-25T13:01:24Z")

</div>

Dear SWI-Prolog user,

SWI-Prolog 10.1.16 is ready for download. This version comes with a lot of  
fixes, notably for comparing cyclic terms, tabling and CLP(Q,R). Highlights:

**Core**

- Standard order of terms and cyclic terms. compare/3 on cyclic terms could  
give a different order depending on how the terms are shared. This is now  
fixed where possible. Some cyclic terms, however, have no standard order.
  - The new Prolog flag `incomparable` can be set to `error` to make  
compare/3, sort/2, etc. raise an exception on such terms rather than  
silently returning an arbitrary order.
  - Added partial\_compare/3, a variant of compare/3 that only decides the  
order if binding variables cannot change it. It returns  
undecided(Sub1,Sub2) or incomparable(Sub1,Sub2) otherwise.

- The Prolog flag `var_prefix` now takes a character. Setting it to `?`  
allows for variables such as `?x`. `true` is still accepted as alias for  
`'_'`.
- Tabling: fixed several crashes and wrong answers in the well-founded  
semantics, notably after abolishing tables and re-evaluating incremental  
tables.
- Printing C backtraces and finding the source of foreign predicates is  
now more than 300 times faster by keeping addr2line (atos on MacOS)  
running.
- write\_term/2 now writes a space between a prefix operator and a symbol,  
e.g. `not -a` rather than `not-a`.
- Fixed listing/2 to pass its options to portray\_clause/3.
- Windows: the `tmp_dir` flag no longer uses 8+3 short file names.

**CLP(Q,R)**

- These libraries had no tests. Now they have a test suite of about 470  
tests and a design document. Writing these revealed many issues that  
are fixed. Notably, the toplevel and copy\_term/3 no longer show internal  
slack variables, solver calls no longer leave choice points, ordering/1  
works, the solver no longer clobbers user global variables and all  
errors are proper error(Formal, Context) terms.

**XPCE (GUI)**

- Class stream (process, socket) now encodes and decodes text, by default  
as UTF-8. Used to send wide characters as raw wchar\_t data and read  
input as ISO Latin-1.

- MacOS: Control-click opens popup menus everywhere. Popup menus are confined to the visual display.

- Drop a pane on the tab bar to give it a tab of its own.

- Epilog and PceEmacs: ^X 2, ^X 3 and ^X 0 to split and close views.

- Many Epilog terminal improvements: Page Up/Down and the complete set of  
function keys are passed to clients such as `less` and `vim`, background  
colour erase, the caret follows the mouse through a selection, fixed  
crash narrowing a window with a full scrollback and properly allow the  
terminal client to read from stdout.

- Many more small fixes 🙂

# SWI-Prolog Changelog since version 10.1.15

[Sep 24 2026]

- FIXED: listing/2 ignored the options it forwards to portray\_clause/3  
The predicate\_options/3 declaration says `pass_to(portray_clause/3, 3)`,  
but list\_clause/6 called portray\_clause/1 and the options were dropped.  
So `listing(M:p/1, [module(M2)])` – the operator table to write the  
clauses with – had no effect.

- MODIFIED: write\_term/2: space between a prefix operator and a symbol  
`not -a` was written `not-a`. That reads back correctly – the reader  
always prefers the prefix operator, and even `zzz - x` parses as  
`zzz(-(x))` – but it suggests an infix `-` to a human reader.

- FIXED: #1531 tmp\_dir flag kept 8+3 short names on Windows TEMP  
is often a short name such as C:\Users\RUNNER~1\AppData\Local\Temp,  
while a file loaded from it is known by its long, on-disk name. A file  
built from the flag therefore did not compare equal to the same file  
as the system reports it, e.g., in the load context or the sources of a  
.qlf file. The flag is now normalised the same way as other file names.

- MODIFIED: Prolog flag var\_prefix now takes a character The flag  
`var_prefix` is either `false` (default) or the character that starts  
a variable. `'_'` is the old `true`; `true` is still accepted as an  
alias, but current\_prolog\_flag/2 now reports `'_'`. Setting it to  
a symbol character such as `?` allows for variables such as `?x`.

[Sep 23 2026]

- ENHANCED: Keep addr2line(1) running to look up C source locations.  
Printing C backtraces and ‘$foreign\_predicate\_source’/2 used to start  
a new addr2line (atos on MacOS) process per address. Looking up all  
foreign predicates in module system now takes 0.07 sec instead of 23  
sec on Linux and 0.09 sec instead of 35 sec on MacOS.

- FIXED: #1530 use-after-free when abolishing all tables  
clear\_variant\_table() releases the tables one by one. Once a table is  
released, atom-GC may reclaim its answer trie while a table released  
later still holds a conditional answer that delays on it, after which  
destroy\_delay\_info() accesses the freed trie. We now destroy the  
delay info of all tables before emptying the variant table.

- FIXED: Internal PL\_compare() calls ignored CMP\_ERROR. PL\_compare()  
returns CMP\_ERROR with a pending exception if the terms cannot  
be compared. Callers now propagate this error. Sset\_exception()  
uses an equality test instead, as raising an exception there would  
replace the pending one.

- ADDED: Prolog flag incomparable to raise an error on incomparable terms.  
Cyclic terms such as A = s(B,0), B = s(A,1) have no standard order.  
compare/3 returns an arbitrary order for them, which silently  
breaks sort/2, library(assoc), library(ordsets), etc. With the  
flag `incomparable` set to `error`, compare/3, sort/2, etc. raise  
representation\_error(standard\_order(Sub1,Sub2)). The flag is thread  
specific.

- ENHANCED: partial\_compare/3: report incomparable cyclic terms.  
If two cyclic terms have no order, i.e., their lexicographic  
comparison runs into a cycle, partial\_compare/3 now unifies Order  
with incomparable(Sub1,Sub2), where Sub1 and Sub2 are the pair of  
subterms at which the comparison cycles.

- FIXED: #1529 compare/3 on cyclic terms depended on sharing.  
When comparing cyclic terms, compare/3 assumes two compounds whose  
comparison is in progress are equal. That is sound for ==/2, but when  
the terms turn out to differ, the order derived from this assumption  
may be wrong, so == terms could compare differently depending on how  
they are shared. If such an assumption was used and one of the terms  
is cyclic, the order is now recomputed by skipping == arguments and  
descending into the first pair that differs. If this descent cycles,  
no consistent order exists and the old result is kept. Also applies  
to partial\_compare/3.

- DOC: CLP(Q,R): answers no longer show slack variables The “Status and  
known problems” section showed {X+Y\>=1} printing as {Y=1-X+\_A, \_A\>=0},  
which no longer happens. Describe what projection does guarantee  
instead, that copy\_term/3 is projected onto the constraint store  
reachable from the copied term rather than onto the term itself,  
and that dump/3 is the way to project onto chosen variables.

[Sep 22 2026]

- ADDED: partial\_compare/3: distinguish stable from unstable comparison.  
partial\_compare/3 is a variant of compare/3 that only decides the  
order of two terms if that decision cannot be invalidated by binding  
a variable. If the order depends on a variable it unifies Order  
with undecided(Sub1,Sub2), the first pair of subterms that could not  
be compared.

[Sep 21 2026]

- FIXED: library(prolog\_colour): handle aggregate/3 and aggregate/4.

- FIXED: xref\_source/1: handle aggregate/3 and aggregate/4

[Sep 20 2026]

- FIXED: representation errors on UTF-16 and wchar\_t streams reperror()  
wrote the &#NN;, \xNN\ or \uNNNN replacement text using put\_byte(),  
which produces garbage on streams that do not use a byte encoding,  
e.g. the UTF-16 encoded console on Windows. It now writes the (ASCII)  
replacement using put\_code().

[Sep 19 2026]

- FIXED: report of a condition that was simplified away A completed  
table answers from its compiled clause (trie\_gen\_compiled/2), and  
compile\_trie\_node() writes a T\_DELAY instruction for every answer  
that is conditional at that moment. Simplification can make such an  
answer unconditional without changing the trie – destroy\_delay\_info()  
clears the delay info of the answer – so nothing discards the compiled  
clause and T\_DELAY keeps pushing a condition that no longer exists.  
call\_delays/2 then reports `undefined` for an answer the table holds  
as unconditionally true:

- FIXED: re-evaluation to leave a table claiming undefined answers  
Preparing a table for re-evaluation removes the delay lists of its  
conditional answers (reeval\_prep\_node()) but left wl-\>undefined  
counting them, while the flags beside it (has\_answers, negative,  
neg\_delayed) are reset. The stale count makes propagate\_to\_answer()  
skip the negative case: it cannot decide tnot(Table) while the table  
may still have undefined answers. So answers of other tables delaying  
on tnot(Table) stayed conditional for ever, although Table had been  
re-evaluated to definitely false:

- FIXED: simplification to write to a freed SCC A worklist  
that is kept when its table completes (because the table has  
conditional answers or other answers delay on it) outlives its  
component: ‘$tbl\_free\_component’/1 frees that while the worklist  
stays attached to the table. propagate\_to\_answer() incremented  
wl-\>component-\>simplifications for every worklist it propagates into,  
and the delay lists lead to tables of older SCCs – so it wrote to  
freed memory. The reproducer is plain Prolog: a chain a/1 → o/1  
 → x/1 where x/1 is incremental and its conditional answer becomes  
unconditional on re-evaluation, which propagates through o/1 into  
a/1 (AddressSanitizer: heap-use-after-free in propagate\_to\_answer(),  
freed by free\_component()).

- FIXED: keep the delay back-references a later abolish needs A  
conditional answer is recorded twice: in its own delay list and, per  
delay element, in the `delays` buffer of the worklist of the table  
it delays on. The latter is what destroy\_depending\_worklists()  
uses to find the tables that depend on a table being abolished,  
and what propagate\_result() walks to simplify them.

- FIXED: crash abolishing a table another table under evaluation delays  
on Abolishing a table destroys the tables holding conditional answers  
that delay on it (destroy\_depending\_worklists()). These were destroyed  
immediately, also when such a table is still under evaluation, and  
free\_worklist() then released the worklist the running evaluation uses.  
The next answer added to it read freed memory:

- FIXED: retractall/1 to report an exception raised while retracting  
If retractClauseDefinition() raises an exception, the loop of the  
`allvars` branch breaks out with rc = false, but the next statement  
unconditionally set rc back to true. retractall/1 thus returned true  
with the exception still pending, so the caller was told the predicate  
was emptied while it still held all its clauses and never saw the error.

- ADDED: edit/0: use last load command as default file. This is used  
as fallback if there is no commandline file.

[Sep 18 2026]

- ADDED: source\_file\_property/2: property loaded(Stamp) Get access to  
the last time this file was loaded.

- ENHANCED: Command line completion on `[Alias(Prefix` for any alias.  
Used to work only for `library(Prefix`.

- ADDED: demo/trace.pl: demo program for the command line tracer A small  
route planner over a cyclic map, meant to be traced rather than run.  
The module documentation holds three exercises: creeping through the  
call, exit, fail and redo ports, using the search commands and spy  
points on a query that fails after a long search, and watching the  
exception port unwind the recursion.

- FIXED: crash in the command line tracer searching for a goal The tracer  
commands /c, /e, /r, /f, /u and /a take an optional goal to search for.  
Comparing that goal against the goal of the current frame addressed  
the arguments of the search goal using the local frame layout and an  
unshifted word, which made the search crash.

[Sep 17 2026]

- FIXED: macOS app: declare NSLocalNetworkUsageDescription Without this  
key macOS may silently block access to hosts on the local network  
from swipl-win and processes started from it, which shows up as  
“No route to host”.

## Package RDF

[Sep 20 2026]

- ENHANCED: MacOS: Control-click asks for a popup menu Use `event  
-\>is\_popup’ rather than testing for the right button going down,  
so that the popups of the RDF diagram and the W3C test browser also  
open on Control-click, which is the secondary click on MacOS.

## Package clpqr

[Sep 23 2026]

- FIXED: CLP(Q,R): copy\_term/3 and the toplevel reported the internal  
store Answers mentioned the slack variables of the simplex tableau:  
{X+Y\>=1} printed as {Y=1-X+\_A, \_A\>=0} and copy\_term/3 returned the  
same. The cause was that attribute\_goals//1 projected onto every  
attributed variable it could reach, so projection had nothing left  
to eliminate and dump/3 ran as an expensive identity. Variables the  
solver introduces for itself – slack variables, the objective of  
minimize/1, the witness of a disequation – are now marked in the  
clpqr\_itf record and left out of the projection target.

- FIXED: CLP(Q,R): delayed goals that had already run stayed attached When  
two variables that both carry delayed non-linear goals are unified,  
the goals of both run and the survivor’s attribute is supposed to be  
cleared; the goals re-attach themselves if they are still non-linear.  
geler.pl wrote

- FIXED: CLP(Q,R): every solver call left a choice point Not one of {}/1,  
inf/2, minimize/1, bb\_inf/3, dump/3, entailed/1 or a plain unification  
with a constrained variable was deterministic, so a program using  
CLP(Q,R) accumulated choice points it could never use and could not  
be last-call optimised. Nearly a tenth of the test suite carried  
[nondet] because of it.

[Sep 22 2026]

- TEST: CLP(Q,R): finish the test suite Three rounds of work on the  
suite, squashed:

- DOC: CLP(Q,R): add a section on working on this code Section 16  
collects what the repairs of section 14 taught, so the next person  
does not rediscover it:

- FIXED: CLP(Q,R): inf/2,4 clobbered the global variable `inf' The same defect as bb_inf/3's `prov\_opt’, in the other optimiser.  
inf\_lin/4 reaches the extremal vertex by pivoting, stores the result,  
then fails so that backtracking undoes the pivots. It kept the result  
in the global variable `inf’, and global variables share one namespace  
with the calling program:

- CLEANUP: CLP(Q,R): remove predicates that have no caller A call graph  
over the package turns up eleven predicates that nothing reaches.  
Removing them takes about 300 lines out and lifts clause coverage  
of the test suite – bv\_\*.pl from 80% to 83%, clpqr/dump.pl from 81%  
to 89%, clpqr/geler.pl from 67% to 80%.

- ENHANCED: CLP(Q): implement the isolating axiom for X = Y^Z The  
manual documents “X = pow(Y,Z) … X and Y are ground =\> 8 = 2^Z”  
and “X = exp(Y,Z) … X and Z are ground =\> 8 = Y^3” as isolating  
axioms, but only CLP(R) ever solved the second one. In CLP(Q),  
{8 =:= Y^3} succeeded with Y unbound and a residual goal, which is  
both undocumented and unsound looking: the query reports success for  
a store it has not decided.

- FIXED: CLP(Q,R): several exceptions were not error/2 terms They  
printed as “Unknown message”, and Q and R disagreed on which of them  
were malformed:

- FIXED: CLP(Q,R): bb\_inf/3,4,5 clobbered the global variable `prov_opt' The branch and bound incumbent was kept in the global variable `prov\_opt’. Global variables share one namespace with the calling  
program, so bb\_inf/3 destroyed a user variable of that name:

- FIXED: CLP(Q,R): a delayed optimisation made copy\_term/3 answers  
unrunnable minimize/1, inf/2 and bb\_inf/3 delay through wait\_linear/3  
until their expression becomes linear. transg//1 reported such a  
pending optimisation by emitting the solver’s own continuation into  
the {}/1 residual:

- FIXED: CLP(Q,R): pivot/2 read the class from the order field pivot/2  
in bv\_q.pl and bv\_r.pl did

- FIXED: CLP(Q,R): a contradictory ordering/1 threw a bare term  
arrangement/2 threw the atom `unsatisfiable\_ordering’, which is not  
an error(Formal, Context) term, so it printed as

- FIXED: CLP(Q,R): ordering/1 had no effect at all Four defects conspired  
to make ordering/1 a no-op, and none of the four worked examples of the  
“Variable Ordering” section of the OFAI manual (Holzbaur, TR-95-09)  
could be reproduced.

- FIXED: CLP(Q,R): non-linear residual goals were reported once per  
variable attribute\_goals//1 reports the constraints on all attributed  
variables of the term at once and then removes the solver attribute  
from each of them, so that the next variable does not report the  
same conjunction again. It only removed clpqr\_itf, but a variable  
that carries nothing but a delayed non-linear goal has no clpqr\_itf  
attribute – only clpqr\_geler. Such a store was therefore printed  
once per variable:

- FIXED: CLP(Q): inverting A = B^X returned a huge inexact rational  
nl\_invertible/4 computed the exponent as

- FIXED: CLP(Q,R): arrange\_pivot/1 could never fire arrange\_pivot/1 in  
clpqr/project.pl guarded on

- FIXED: CLP(R): #(pi) was 3.14259265 instead of 3.14159265 The two  
digits were transposed, making #(pi) and #(p) wrong by 1.0e-3, four  
orders of magnitude above the solver’s own epsilon of 1.0e-10.

- TEST: Add a test suite and design document for CLP(Q,R) The clpq  
and clpr libraries had no tests at all. This adds test\_clpq.pl  
(242 tests) and test\_clpr.pl (229 tests), registered with CMake as  
`ctest -R clpqr:`, and doc/design.md describing the implementation  
and the literature it derives from.

## Package json

[Sep 20 2026]

- FIXED: #3 emitting non-BMP characters on Windows json\_write/2 and  
friends turned characters outside the BMP into garbage on Windows.  
json.c has no config.h, so SIZEOF\_WCHAR\_T was never defined and  
the code that combines UTF-16 surrogate pairs into a code point was  
compiled out. As a result the surrogates were passed to Sputcode(),  
which cannot represent them.

## Package sgml

[Sep 20 2026]

- ENHANCED: MacOS: Control-click opens the insert menu Use `event  
-\>is\_popup’ rather than testing for the right button going down,  
so that the SGML mode’s insert menu also opens on Control-click,  
which is the secondary click on MacOS.

## Package xpce

[Sep 25 2026]

- ENHANCED: demo(ispell). Deal with Unicode, do not block the event loop.

- ENHANCED: class stream (process, socket) encodes and decodes text Added  
stream\<-\>encoding, default `utf8` (class variable). Output of -\>append,  
-\>format, etc. is encoded using the Prolog stream layer and input is  
decoded into characters before it is handed to the record\_separator,  
input\_message and \<-read\_line. Previously wide text was written as  
raw wchar\_t data and input was read as ISO Latin-1.

- FIXED: SDL: process-\>wait hung when the child’s output pipe reached  
EOF The fd watcher only reacted to POLLIN, but Linux reports EOF  
on a pipe as POLLHUP without POLLIN. The EOF was never delivered,  
so the input side was never closed and process-\>wait looped forever  
(while the watcher thread busy-looped on poll()).

- FIXED: xpce: a fixed graphical moved to another window was not painted  
there Moving the grip of a pane between the windows of its tabs left it  
displayed, so -\>display\_fixed did not ask for it to be painted. Also  
apply the scroll offset of the fixed layer in changedImageGraphical().

[Sep 24 2026]

- FIXED: xpce: a hidden tab forgot the pane clicked last Clicking a  
pane moved the keyboard focus without telling its tab\_frame, so a  
tab switched away from fell back on the pane last made current by  
-\>current, e.g., the thread monitor split beside a console. The tab  
was relabelled after it and gave it the focus when raised again.

- ENHANCED: Epilog: Emacs window keys ^X 2, ^X 3 and ^X 0; ^X 0 closes  
a view beside other panes Epilog now splits with ^X 2 and ^X 3 and  
closes a terminal with ^X 0. In both Epilog and PceEmacs, ^X 0 closes  
the only view of a tab as long as the window shows something else,  
such as a console beside the editor. New method pane-\>last\_in\_frame  
tells whether closing a pane would leave its window empty.

- FIXED: xpce: crash printing to a closed Prolog output stream during  
halt Console output from xpce, e.g., debug messages while windows  
are destroyed, now goes to C stderr if Prolog’s output stream is gone.

- ENHANCED: PceEmacs: ESC aborts the argument prompter, as ^G does  
Co-Authored-By: Claude Opus 5.5 (1M context) [noreply@anthropic.com](mailto:noreply@anthropic.com)

- ENHANCED: PceEmacs: support the Prolog flag var\_prefix set to a  
character Singleton replacement renames `?x` to `?_x` or `?_` if the  
buffer sets `var_prefix` to `?`, and reading a selection and finding  
dependencies use the buffer’s var\_prefix.

[Sep 23 2026]

- FIXED: terminal\_image: size in cells ignored the initial size in  
pixels A terminal\_image started with 25 rows and the width of its  
pixel size without the margins, and only a later change of size or  
font computed these from the pixel size. A terminal that was never  
resized had more rows than it showed, so a line feed on its last  
visible row did not scroll.

- ENHANCED: Epilog: lines scrolled into the terminal take the background  
colour As xterm’s background colour erase (bce), the empty lines of IL,  
DL, SU, SD and a scroll of the region, as well as a line a line feed  
scrolls into the window, are painted in the current background colour.

- FIXED: Epilog: background colour of an erased line leaked into later  
lines An erase in line with a background colour records that colour  
on the line. Freeing a line without text (as opened by IL, DL or a  
scroll region) did not reset it, so a line later written in the same  
slot was painted on the old background. ED 1 also left the recorded  
erase of the lines it cleared in place.

- ENHANCED: Epilog: the link popup item opens non-Prolog files and  
copies URLs A file:// link to a Prolog file is still consulted.  
Other file links are opened with the desktop application and other  
links are copied.

- FIXED: Epilog: hovering a link that wraps over lines arms all of it  
Also rejoin the pieces of a link when rewrapping moves part of it to  
a line holding the rest, so a resize no longer leaves it split.

- FIXED: lazy method binding refused all nested binds Binding a method  
runs Prolog, which may need to bind other methods, e.g. when a signal  
handler halts inside the callback. Only refuse re-binding the same  
method, which would loop.

[Sep 22 2026]

- FIXED: complete font.system\_fonts and font.pango\_families with the  
built-in values A Defaults file that overrules one of these class  
variables replaces it entirely, so every alias or family it left  
out was simply undefined. makeBuiltinFonts() now walks over the  
value class font declares itself after the user defined values have  
been loaded. As neither fontAlias() nor the family loader overwrites  
an entry that is already there, this adds exactly the omitted ones,  
so a Defaults file only needs to mention what it wants to change.

[Sep 21 2026]

- ENHANCED: xpce: complete the SDL3 function key coverage  
keycode\_to\_name() stopped at F12 and the four arrows, so the second  
row of function keys, Insert, the VT220 editing keys and the Sun and  
HP keys beside the alphabet produced no event at all. They are now  
matched to the event ids xpce has had for them all along – most  
of initial\_tree under `named_function' was unreachable -- with f13..f24 and the clipboard keys added to the tree, and the terminal sends the ones that have a meaning to a client: Insert as `CSI 2~‘,  
Find and Select as `CSI 1~' and `CSI 4~’, the middle of the keypad as  
`CSI E’ and F13..F24 as the Shift+F1..F12 that xterm’s terminfo entry  
spells them as.

- FIXED: do not re-enter the redraw from the live resize watch SDL runs  
an event watch at SDL\_PushEvent() time, before the event is queued,  
so an SDL call made while we are painting lands in live\_resize\_watch()  
with a drawing context open: `frame -\>size’ asked for by a -\>compute  
or a -\>\_redraw\_area is enough. It then laid the frame out and painted  
it from under that context, which paints a window a second time inside  
its own redraw – clipped to the area the outer redraw was given –  
and lets ws\_geometry\_window() destroy the backing store the open  
context draws into. This is what printed “d\_window(…): Context is  
already open”. Leave the event in the queue for the main loop instead.

- FIXED: crash narrowing a terminal window whose scrollback is full  
Rewrapping a narrower terminal needs more lines than the ring holds,  
so the oldest text scrolls off to pay for them. If the line to be  
wrapped was itself the oldest one, rlc\_shift\_lines\_down() dropped  
exactly that line to make the room, leaving the wrap to copy from a  
line that no longer held its text: a segmentation fault on shrinking  
a console with a full buffer. Drop the head of the line instead,  
which is the text that scrolls off anyway.

[Sep 20 2026]

- FIXED: MacOS: closing a window did not reactivate another one of  
the app AppKit activates the next window of the application when the  
key window closes, and does not do so for the windows SDL creates:  
closing the window opened with Command-N left the one it was opened  
from visible but inactive, with no caret and no keys. Hand the  
keyboard on ourselves.

- FIXED: tab\_frame: closing a pane moved the focus to the far side of  
the tab `tab\_frame -\>update\_current’ fell back on the first window of  
\<-windows, which is the order the windows were displayed in rather than  
the order they are laid out in. Closing a pane from a split therefore  
pulled the focus to whichever pane happened to be displayed first.  
Hand it to the pane that takes over the room instead: the one after  
it, and the one before it when it was the last.

- ENHANCED: MacOS: Control-click asks for a popup menu Control-click is  
the documented alternative for the secondary click on MacOS. The new  
`event -\>is\_popup’ tests whether an event asks for a popup (context)  
menu: the right button going down or, on MacOS, Control-left. Class  
popup\_gesture activates on it, as does the code that opens a popup  
without using a gesture: list\_browser, toc windows, the inspector,  
the thread monitor, PceEmacs fragments and the Epilog terminal.

- FIXED: terminal: pass Page Up and Page Down to the client The page keys  
were bound to -\>cursor\_page\_up and -\>cursor\_page\_down, which scroll  
the scroll back whatever is running on the terminal. A client never  
saw them, which left `less', `man’, `vim' and every other program that is paged with those keys a key short. They now follow the rule the control characters and the function keys already follow: while a client owns the pty, or while the alternate screen is up, the keys are its own and are sent as xterm's `CSI 5~’ and `CSI 6~'.  
With nothing running they scroll the scroll back as before, and Shift  
is the way out of the client, as it is for the wheel.

- ENHANCED: drop a pane on the tab bar to give it a tab of its own  
Dragging a pane by its grip onto another window splits that window;  
dropping it on the tab bar – the label row, or the menu bar above it,  
which is the whole of the bar while a single tab hides its label –  
now gives the pane a tab of its own instead. Where in the row the  
pointer is says where the new tab goes, and a ghost of the label the  
tab will carry shows that while the pointer is there.

- FIXED: xpce: typing did nothing in a window that claims the keyboard  
focus After a confirmer, popup or completer that holds a pointer grab  
took the focus away and closed again, SDL text input stayed disabled  
for the frame that regains the focus. A terminal or editor then  
still drew an active caret, but typing printable characters had no  
effect until the focus was moved to another window and back.

[Sep 19 2026]

- FIXED: terminal: let the caret follow the mouse through a selection  
The input caret of the terminal now moves as soon as the button goes  
down and travels with a drag, so that it ends up at the end of the  
selection, as it does in an editor. It used to wait for the button  
to come up and then only move if no selection had been made. A click  
or drag outside the line being edited still leaves the caret alone.

- FIXED: colour and hyperlinks for output from the xpce thread  
Epilog enabled the `color_term` and `hyperlink_term` flags  
using set\_prolog\_flag/2, which creates a thread-local copy of  
the flag once a second thread is running. The xpce event thread  
therefore kept the global `false` and messages it printed, e.g. from  
in\_pce\_thread(xref\_source(File)), reached the terminal as plain text  
without a clickable source location.

[Sep 18 2026]

- FIXED: “No implementation for: -\>current\_terminal” on a detached  
terminal A terminal that is in no window of the IDE told a plain frame  
it had the keyboard, and a plain frame knows nothing about terminals.

- STYLE: draw the pullright marker as a Windows style “\>” Popup menu  
items with a pullright submenu are now marked with a plain chevron  
drawn by draw\_popup\_indicator() rather than an image or a 3d triangle.  
This removes `menu <->popup_image` and the global @ms\_left\_arrow\_image.

- FIXED: Epilog: open the client’s stdout and stderr read-write The  
three descriptors a terminal hands its client are the terminal as far  
as it, and anything it starts, is concerned, and a terminal is one  
device that a program both reads and writes. Ours opened stdout and  
stderr write-only, so a client that asked the terminal a question on  
stdout could not read the answer back from it, which is how termenv,  
and with it every Go program with a terminal user interface, learns the  
colours it draws on. Its read failed with EBADF, the answer stayed in  
the input queue, and the next program to read stdin found an escape  
sequence it never asked for: `gh run download’ in an Epilog OS shell  
died with “could not prompt: unexpected escape sequence from terminal”.

[Sep 17 2026]

- CLEANUP: Remove all usage of old font names. E.g. family `screen`,  
style `normal`, etc.

- FIXED: MacOS: keep popup menus inside the display SDL’s popup  
constraining is disabled on MacOS because it misplaces popups on  
secondary displays. Clamp the popup ourselves against the usable  
bounds of the display holding it.

- TEST: Only run the too-wide glyph test if SWIPL\_TEST\_FAIL\_ON\_UNLIKELY=y  
The outcome depends on the installed fonts. With only DejaVu, U+23BF  
has no glyph and the missing-glyph box crosses the cell boundary.

- FIXED: PceEmacs failed to start if there is no config directory If  
HOME does not exist, user\_app\_config has no expansion and locating the  
bookmark and pane layout stores raised an existence error, so @emacs  
could not be created. The stores now live in memory only in that case.
