Add Live Activities as configurable bar items (#30)

* Add Live Activities as configurable bar items

* Fix activity owner monitoring and Morph list interactions

* Keep activity rows stable during updates and tighten their layout

* Use subtle theme tint for activity cards

* Tighten activity title and progress spacing

* Use theme accent for active progress rings

* Fit compact activities to measured titles and badge

* Expose Live Activities display controls in Settings

* Open activity terminals and streamline the Morph header

* Give Morph continuous icons and prioritize workspace navigation

* Show activity progress inside its ring

* Inset activity progress rings and strengthen their stroke

* Fix activity lifecycle, command output handling and list navigation

* Reduce activity update work and harden publisher lifetimes

* Set up Live Activities for the detected user shell during installation

* Document Activities setup, daily use and troubleshooting

* Preserve Morph idle width across empty activity updates

* Fix dimming flicker, hide inactive music tab and consolidate Activities docs

* Fix panel clearance across styles and match previews to theme settings

* Prevent panel blur from repainting neighboring islands

---------

Co-authored-by: Samuel Onoja <[email protected]>
This commit is contained in:
Samuel OnojaandSamuel Onoja authored and GitHub committed 2026-10-12 22:52:25 +01:00
1 parent 7a7df0106c
commit 6b0d903ad0
107 files changed
+9340 -291

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
"""Hook composition/status checks use a fake publisher and never run metadata."""
from pathlib import Path
import fcntl
import json
import os
import pty
import select
import shutil
import subprocess
import tempfile
import termios
import time
import unittest
ROOT = Path(__file__).resolve().parents[2]
class ActivityHooks(unittest.TestCase):
def test_real_interactive_commands_publish_start_and_exit_status(self):
for shell in ('bash', 'fish'):
if not shutil.which(shell):
continue
with self.subTest(shell=shell), tempfile.TemporaryDirectory() as directory:
root = Path(directory)
publisher = root / 'feresectl'
publisher.write_text('#!/usr/bin/env python3\nimport json, os, sys\n'
'with open(os.environ["ACTIVITY_TEST_LOG"], "a") as f:\n'
' f.write(json.dumps(sys.argv[1:]) + "\\n")\n')
publisher.chmod(0o755)
log = root / 'calls'
startup = root / 'startup'
dependency = f'source "{ROOT}/contrib/activities/bash-preexec/bash-preexec.sh"\n' if shell == 'bash' else ''
startup.write_text(dependency + f'source "{ROOT}/contrib/activities/ferese.{shell}"\n')
master, slave = pty.openpty()
env = dict(os.environ, HOME=directory, XDG_CONFIG_HOME=directory, HISTFILE='/dev/null',
PATH=str(root) + ':' + os.environ['PATH'], ACTIVITY_TEST_LOG=str(log), TERM='dumb', PS1='ACTIVITY_PROMPT> ')
command = (['bash', '--noprofile', '--rcfile', str(startup), '-i'] if shell == 'bash' else
['fish', '--no-config', '-i', '--init-command', f'function fish_prompt; printf "ACTIVITY_PROMPT> "; end; source {startup}'])
process = subprocess.Popen(command, env=env, stdin=slave, stdout=slave, stderr=slave,
start_new_session=True, preexec_fn=lambda: fcntl.ioctl(0, termios.TIOCSCTTY, 0))
os.close(slave)
trace = b''
def wait_for(predicate):
nonlocal trace
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
if select.select([master], [], [], .05)[0]:
trace += os.read(master, 65536)
if predicate():
return
self.fail(trace.decode(errors='replace'))
def calls():
return [json.loads(line) for line in log.read_text().splitlines()] if log.exists() else []
try:
# Wait for the first prompt before sending an interactive command.
wait_for(lambda: b'# ' in trace or b'$ ' in trace or b'> ' in trace)
os.write(master, b'false\n')
wait_for(lambda: any(call[:2] == ['activity', 'end'] for call in calls()))
records = calls()
start = next(call for call in records if call[:2] == ['activity', 'hook'])
end = next(call for call in records if call[:2] == ['activity', 'end'])
self.assertEqual(start[start.index('--command') + 1], 'false')
self.assertEqual(end[2], start[2])
self.assertEqual(end[end.index('--result') + 1], 'fail')
finally:
process.terminate()
try:
process.wait(timeout=2)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
os.close(master)
@unittest.skipUnless(shutil.which("fish"), "fish unavailable")
def test_fish_preserves_status_and_does_not_execute_metadata(self):
script = f'''function feresectl; return 71; end
source "{ROOT}/contrib/activities/ferese.fish"
__ferese_preexec 'echo SHOULD_NOT_EXECUTE'
false
__ferese_postexec
set saved $status
printf 'status=%s\\n' $saved
'''
result = subprocess.run(["fish", "--no-config", "-i", "-c", script], capture_output=True, text=True, timeout=10)
self.assertIn("status=1", result.stdout)
self.assertNotIn("SHOULD_NOT_EXECUTE", result.stdout)
def test_bash_preserves_status_and_never_executes_captured_command(self):
script = f'''
feresectl() {{ return 71; }}
source "{ROOT}/contrib/activities/bash-preexec/bash-preexec.sh"
source "{ROOT}/contrib/activities/ferese.bash"
_ferese_arm
_ferese_preexec 'echo SHOULD_NOT_EXECUTE'
false
_ferese_finish
saved=$?
printf 'status=%s\\n' "$saved"
'''
result = subprocess.run(["bash", "--norc", "-i", "-c", script], capture_output=True, text=True, timeout=10)
self.assertIn("status=1", result.stdout)
self.assertNotIn("SHOULD_NOT_EXECUTE", result.stdout)
def test_bash_preserves_existing_debug_trap(self):
script = f'''trap ':' DEBUG
before=$(trap -p DEBUG)
source "{ROOT}/contrib/activities/ferese.bash"
after=$(trap -p DEBUG)
[[ "$before" == "$after" ]]
'''
result = subprocess.run(["bash", "--norc", "-i", "-c", script], capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
@unittest.skipUnless(shutil.which("zsh"), "zsh unavailable")
def test_zsh_composes_hooks_and_preserves_status(self):
script = f'''feresectl() {{ return 71; }}
existing() {{ :; }}
autoload -Uz add-zsh-hook
add-zsh-hook precmd existing
source "{ROOT}/contrib/activities/ferese.zsh"
(( ${{precmd_functions[(Ie)existing]}} > 0 )) || exit 4
_ferese_preexec 'echo SHOULD_NOT_EXECUTE'
false
_ferese_precmd
saved=$?
printf 'status=%s\\n' "$saved"
'''
result = subprocess.run(["zsh", "-f", "-i", "-c", script], capture_output=True, text=True, timeout=10)
self.assertIn("status=1", result.stdout)
self.assertNotIn("SHOULD_NOT_EXECUTE", result.stdout)
if __name__ == "__main__":
unittest.main()