summaryrefslogtreecommitdiff
path: root/test/appearance_gallery.py
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-11 12:38:56 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit682e237df7e8b22f820d14a4adee58e6e2f84268 (patch)
tree5d888cb46f1a49e89fc55e2a4c232d3af0265b1d /test/appearance_gallery.py
parent6878e1c309172d624c0d7a3555f6f0217ae6770e (diff)
downloadpardes-682e237df7e8b22f820d14a4adee58e6e2f84268.tar.gz
pardes-682e237df7e8b22f820d14a4adee58e6e2f84268.zip
trunk: resume before the Reload experiment
Empty marker on the last pre-Reload change. Keep the Reload experiment on reload (3801914), its first change on reload-start (200a1fc), and the unfinished performance investigation on reload-perf-wip.
Diffstat (limited to 'test/appearance_gallery.py')
-rw-r--r--test/appearance_gallery.py213
1 files changed, 213 insertions, 0 deletions
diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py
new file mode 100644
index 00000000..c80d1a33
--- /dev/null
+++ b/test/appearance_gallery.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3
+"""Review native palettes, compact tags, sized fonts and pets in an owned SDL session."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import subprocess
+import time
+
+from fs import session
+from ui_review import capture, command, panes, require, wait_for
+
+
+THEMES = ('forge', 'lagoon', 'solarium', 'spectrum', 'harvest', 'clay')
+NOTES = b'''Pardes / workspace review
+
+New belongs to the column.
+Tty opens a terminal.
+Togglettymode switches its input mode.
+
+Font MartianMono-NrRg:18
+TaglineSize 80
+
+Native palettes
+ forge familiar charcoal
+ lagoon oceanic blue
+ solarium quiet solar tones
+ spectrum vivid syntax
+ harvest warm earth
+ clay soft rose and pine
+
+Pet cat / Pet frog / Pet off
+Companions stay in unused workspace-tag space.
+'''
+PREFERENCE_NOTES = b'''Pardes / contrast studies
+
+Comments carry their weight.
+Line numbers stay in the background.
+Current line: bold quiet ink, no bright block.
+
+Forge: neutral, near-black charcoal
+ Theme forge
+ Theme forge_black
+ Theme forge_soft
+
+Orchard: crisp woodland color
+ Theme orchard
+ Theme orchard_black
+
+Dusk: intentionally soft and warm
+ Theme dusk
+
+ThemeSel: Pardes themes come first.
+'''
+
+
+def run(args):
+ root = args.output.resolve()
+ root.mkdir(parents=True, mode=0o700, exist_ok=True)
+ root.chmod(0o700)
+ config = root / 'config/pardes'
+ config.mkdir(parents=True, exist_ok=True)
+ (config / 'init').write_text('Shell /bin/sh\nTheme forge\nFont MartianMono-NrRg:18\nTaglineSize 80\n')
+ source_path = args.project.resolve() / 'gossip/src/crds_value.rs'
+ original = source_path.read_bytes()
+ name = 'appearance-gallery-' + str(os.getpid())
+ launch = ['-c', 'stty cols 150 rows 48; exec "$@"', name,
+ str(args.binary.resolve()), '--9p=' + name, str(source_path)]
+ inherited = {'PARDES_TEST': '1', 'PARDES_TEST_CAPTURE_DIR': str(root),
+ 'PARDES_TEST_COLS': '150', 'PARDES_TEST_ROWS': '48',
+ 'SDL_VIDEODRIVER': 'x11'}
+ if args.live_window:
+ inherited = {'SDL_VIDEODRIVER': 'x11'}
+ handles = {}
+ report = {'themes': [], 'pets': [], 'os_windows': []}
+ with session('/bin/sh', root, name, socket_name=name, tty=True,
+ launch=launch, inherited=inherited, terminal=handles) as (client, _):
+ source = next(serial for serial, tag in panes(client).items() if str(source_path) in tag)
+ before = set(panes(client))
+ command(client, source, 'Newcol')
+ right = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'right column missing')
+ client.write(f'/self/pane/{right}/ctl', f'name {root / "Review"}\n'.encode())
+ client.write(f'/self/pane/{right}/body', PREFERENCE_NOTES if args.theme_selector else NOTES, truncate=True)
+ client.write(f'/self/pane/{right}/ctl', b'clean\n')
+ before = set(panes(client))
+ command(client, right, 'Tty')
+ terminal = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'terminal missing')
+ client.write(f'/self/pane/{terminal}/pty/data', b"PS1='$ '; printf '\\033[2J\\033[H\\033[1;36mPardes workspace\\033[0m\\n\\n'; printf 'Agave source: read-only\\nTerminal: ready\\n\\n'; printf '\\033[32mSmall touches. Same workflow.\\033[0m\\n'\r")
+ require(wait_for(lambda: b'Small touches. Same workflow.' in client.read(f'/self/pane/{terminal}/body')), 'terminal output missing')
+
+ def config_report():
+ before = set(panes(client))
+ command(client, right, 'Config')
+ pane = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'Config output missing')
+ text = client.read(f'/self/pane/{pane}/body').decode()
+ client.write(f'/self/pane/{pane}/ctl', b'del\n')
+ return text
+
+ settings = config_report()
+ require('Font effective size: 18.00 pixels' in settings, 'configured font size not applied: ' + settings)
+ require('TaglineSize: 80' in settings, 'configured tag size missing')
+ (root / 'effective-config.txt').write_text(settings)
+ report['font_effective_pixels'] = 18
+ report['tagline_percent'] = 80
+ for serial, tag in panes(client).items():
+ require(' New ' not in tag, 'pane still contains New')
+ require('Newtty' not in tag, 'old terminal name remains')
+ require('Togglettymode' in panes(client)[terminal], 'terminal mode action missing')
+
+ def owned_window():
+ result = subprocess.run(['xdotool', 'search', '--onlyvisible', '--pid', str(handles['pid'])],
+ text=True, capture_output=True)
+ ids = result.stdout.split()
+ if not ids:
+ return None
+ window = ids[0]
+ actual_pid = subprocess.check_output(['xdotool', 'getwindowpid', window], text=True).strip()
+ require(actual_pid == str(handles['pid']), 'OS capture window is not owned')
+ return window
+
+ window = require(wait_for(owned_window), 'owned SDL window missing')
+ subprocess.run(['xdotool', 'windowsize', window, '1440', '960'], check=True)
+
+ def take_frame(label):
+ capture(client, root, label, not args.live_window)
+ if not args.live_window:
+ return
+ window = require(owned_window(), 'owned SDL window disappeared')
+ subprocess.run(['magick', 'import', '-window', window, str(root / (label + '.png'))], check=True)
+ if window not in report['os_windows']:
+ report['os_windows'].append(window)
+
+ for theme in args.themes:
+ command(client, right, 'Theme ' + theme)
+ if args.theme_selector:
+ # Keep both quiet active gutter ink and vivid comments in view.
+ client.write(f'/self/pane/{source}/ctl', f'look {source_path}:117\n'.encode())
+ take_frame('classic-' + theme)
+ if args.theme_selector:
+ frame = client.screen()
+ row = frame['cursor']['y'] * frame['cols']
+ gutter = frame['styles'][frame['cells'][row + 2][1]]
+ margin = frame['styles'][frame['cells'][row + 1][1]]
+ require(gutter['bg'] == margin['bg'], 'active line number has a background block in ' + theme)
+ require(gutter['bold'], 'active line number is not bold in ' + theme)
+ previous = frame['styles'][frame['cells'][row - frame['cols'] + 2][1]]
+ require(not previous['bold'], 'inactive line number is bold in ' + theme)
+ active_tag = frame['styles'][frame['cells'][2 * frame['cols'] + 2][1]]
+ inactive_tag = frame['styles'][frame['cells'][2 * frame['cols'] + frame['cols'] // 2 + 2][1]]
+ def ink_polarity(style):
+ return sum(style['fg']['rgb']) - sum(style['bg']['rgb'])
+ require(ink_polarity(active_tag) * ink_polarity(inactive_tag) > 0,
+ 'focused tag inverts the inactive tag in ' + theme)
+ report['active_gutter_bold_without_block'] = True
+ report['focus_tags_without_inversion'] = True
+ report['themes'].append(theme)
+ command(client, right, 'Theme forge')
+ if args.theme_selector:
+ command(client, terminal, 'Collapse')
+ before = set(panes(client))
+ command(client, right, 'ThemeSel')
+ selector = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)),
+ 'ThemeSel missing')
+ body = client.read(f'/self/pane/{selector}/body').decode()
+ native, legacy = body.split('# Legacy and imported themes', 1)
+ for theme in ('forge_black', 'forge_soft', 'orchard_black'):
+ require('Theme ' + theme + '\n' in native, 'native contrast variant below legacy heading')
+ require('Theme helix\n' in legacy, 'legacy theme section missing')
+ require('Theme orchard' in ''.join(c[0] for c in client.screen()['cells']),
+ 'ThemeSel did not open at native first entry')
+ take_frame('theme-selector')
+ report['native_themes_first'] = True
+ client.write(f'/self/pane/{selector}/ctl', b'del\n')
+ command(client, terminal, 'Collapse')
+ for pet in (() if args.skip_pets else ('cat', 'frog', 'off')):
+ command(client, right, 'Pet ' + pet)
+ settings = config_report()
+ require('Pet: ' + pet + '\n' in settings, 'pet setting not applied')
+ # Returning focus to the source closes all transient report state.
+ client.write(f'/self/pane/{source}/ctl', f'look {source_path}:1\n'.encode())
+ take_frame('pet-' + pet)
+ if pet != 'off':
+ frame = client.screen()
+ original_tag = frame['cells'][:frame['cols']]
+ time.sleep(1)
+ take_frame('pet-' + pet + '-later')
+ frame = client.screen()
+ require(original_tag == frame['cells'][:frame['cols']], 'pet animation changed tag text or styles')
+ def band_digest(label):
+ pixels = subprocess.check_output(['magick', str(root / (label + '.png')),
+ '-crop', '1500x20+0+0', '-depth', '8', 'RGB:-'])
+ return hashlib.sha256(pixels).hexdigest()
+ require(band_digest('pet-' + pet) != band_digest('pet-' + pet + '-later'),
+ 'pet did not animate in workspace tag')
+ report['pets'].append(pet)
+ require(client.read(f'/self/pane/{source}/body') == original == source_path.read_bytes(), 'Agave source changed')
+ report['source_unchanged'] = True
+ client.write(f'/self/pane/{terminal}/pty/data', b'exit\r')
+ (root / 'report.json').write_text(json.dumps(report, indent=2))
+ print(json.dumps(report, indent=2))
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('binary', type=Path)
+ parser.add_argument('output', type=Path)
+ parser.add_argument('--project', required=True, type=Path)
+ parser.add_argument('--live-window', action='store_true', help='capture the owned OS window without offscreen test mode')
+ parser.add_argument('--themes', nargs='+', default=THEMES, help='themes to compare in order')
+ parser.add_argument('--skip-pets', action='store_true', help='review palettes without repeating pet animation tests')
+ parser.add_argument('--theme-selector', action='store_true', help='capture native-first picker and focused source gutter')
+ run(parser.parse_args())