perf(quickshell): smooth dock hover and soften floating shadow
This commit is contained in:
parent
d4e2a0013e
commit
91c39ecf9b
31 changed files with 2280 additions and 32 deletions
137
docs/quickshell-dock-tela-polish-v2.md
Normal file
137
docs/quickshell-dock-tela-polish-v2.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# QuickShell Dock Tela — Polish V2
|
||||
|
||||
Livraison isolée du 22 octobre 2026, base `d4e2a001`, branche
|
||||
`quickshell-dock-tela-polish-v2`, worktree
|
||||
`/home/fy59/.dotfiles-worktrees/quickshell-dock-tela-polish-v2`.
|
||||
La configuration du bureau actif n’a pas été modifiée ou rechargée.
|
||||
|
||||
## Cause et correction du hover
|
||||
|
||||
La V1 animait la largeur de chaque case de la `Row` et celle de chaque
|
||||
`ApplicationIcon`. Chaque déplacement du pointeur modifiait donc les positions
|
||||
suivantes, la largeur de la rangée et potentiellement celle de la surface.
|
||||
De plus, `ApplicationIcon` calcule le `sourceSize` de son image depuis sa largeur :
|
||||
la variation de taille demandait de nouvelles résolutions de rasterisation pour
|
||||
les SVG Tela asynchrones. Ces deux chemins constituent les causes structurelles
|
||||
identifiées des saccades et du risque de clignotement. Aucun profilage GPU du
|
||||
bureau physique n’est revendiqué.
|
||||
|
||||
Les cases gardent désormais une largeur de 54 px (plus le séparateur éventuel),
|
||||
un pas de 57 px et des cibles souris fixes. Les images restent à 46 × 46 px,
|
||||
avec une résolution demandée fixe de 92 × 92 px. Seul leur `scale` varie de
|
||||
1 à 1,39, ancré en bas, avec une interpolation OutCubic de 80 ms. La taille
|
||||
visuelle maximale est ainsi d’environ 64 px. Les deux marges de 12 px à
|
||||
l’intérieur du viewport protègent aussi les icônes agrandies aux extrémités,
|
||||
y compris après défilement sur un petit écran.
|
||||
|
||||
L’influence du pointeur est une courbe smoothstep, symétrique, de rayon 76 px.
|
||||
Ce rayon est inférieur à un pas et demi : seules la case la plus proche et ses
|
||||
voisines immédiates peuvent recevoir une cible de grossissement non nulle.
|
||||
Les positions de référence restent indépendantes de la transformation.
|
||||
Les transitions peuvent brièvement finir leur interpolation après la sortie
|
||||
du rayon. Aucun IPC, blur, chargement d’image ni relayout n’est déclenché par
|
||||
l’animation elle-même. Les petits calculs d’influence restent liés au pointeur ;
|
||||
aucun timer de polling ou délai de regroupement supplémentaire n’est nécessaire.
|
||||
|
||||
## Fond flottant
|
||||
|
||||
Le voile principal passe de 89 % à **16 % d’opacité**, sa hauteur de 70 à 58 px,
|
||||
avec une bordure de 1 px à 9 % d’opacité. Une `RectangularShadow` QtQuick.Effects,
|
||||
de géométrie fixe, produit l’ombre diffuse (blur 24, spread 2, noir à 32 %).
|
||||
Elle ne capture ni les icônes ni le bureau. Le mode de contraste élevé conserve
|
||||
un fond plus soutenu à 94 % et une bordure à 50 % pour la lisibilité.
|
||||
|
||||
La règle SwayFX du seul namespace `labfy-sway-dock` désactive le flou du fond.
|
||||
Le compromis retenu est un verre teinté très transparent, sans réfraction ni
|
||||
flou des fenêtres derrière. Le rendu ne dépend donc plus d’un flou compositeur
|
||||
recalculé pendant le hover. Les effets de la barre restent inchangés.
|
||||
`RectangularShadow` nécessite Qt ≥ 6.9 ; l’hôte testé utilise Qt 6.12.0,
|
||||
QuickShell 0.3.2 et SwayFX 0.6 (Sway 1.12.0).
|
||||
|
||||
## Contrats préservés
|
||||
|
||||
La machine auto-hide conserve sa bande de 4 px, son armement de 180 ms,
|
||||
sa grâce de sortie de 450 ms et son animation verticale de 210 ms.
|
||||
`WlrKeyboardFocus.None`, couche Top, `exclusiveZone: 0` et le masque d’entrée
|
||||
restent en place. Aucune place permanente n’est réservée à l’écran.
|
||||
Le menu, `FavoritesService`, la résolution Tela et le format des favoris
|
||||
partagés sont inchangés. Aucun fichier Session V2, Keyring, PAM, greetd ou
|
||||
logique de session n’a été modifié.
|
||||
|
||||
## Validation
|
||||
|
||||
Les tests s’exécutent dans un Sway privé headless, avec HOME, XDG,
|
||||
sockets Wayland/Sway et profil Firefox privés, sans bus de session hérité. Les enfants sont arrêtés par
|
||||
le laboratoire. Les journaux et résultats sont conservés dans
|
||||
[validation/quickshell-dock-tela-polish-v2](validation/quickshell-dock-tela-polish-v2/).
|
||||
|
||||
Commandes reproductibles depuis le worktree :
|
||||
|
||||
```sh
|
||||
python -m unittest discover -s tests/applications -v
|
||||
node tests/applications/test_model.js
|
||||
python -m unittest discover -s tests/dock -v
|
||||
node tests/dock/test_models.js
|
||||
python -m unittest discover -s tests/session_v2 -v
|
||||
python tests/dock/headless.py
|
||||
python tests/dock/shell_load.py
|
||||
python tests/dock/session_compat.py
|
||||
python tests/session_v2/validate_keyring.py --sway /usr/bin/sway
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Résultats : 7 tests applications, 6 tests dock, 93 tests Session V2 backend,
|
||||
les deux suites JavaScript, le chargement du shell complet, le scénario dock
|
||||
et les scénarios Session V2/Keyring avec dock passent. Le validateur Keyring
|
||||
complet termine avec ses onze contrôles à zéro (succès).
|
||||
|
||||
Le test dock vérifie notamment :
|
||||
|
||||
- 73 positions latérales sans changement de géométrie, sans changement d’état
|
||||
des images et avec au plus trois influences non nulles ;
|
||||
- 4,5 secondes de balayage continu, un mouvement souris par frame Qt,
|
||||
sans changement de géométrie ni d’état de chargement des images ;
|
||||
- extrémités non rognées après défilement sur une sortie de 400 px ;
|
||||
- auto-hide, maintien pendant le menu contextuel, drag réel et redémarrage vide ;
|
||||
- synchronisation menu/dock/client externe et réordonnancement durable ;
|
||||
- absence de vol de focus vers le dock, fenêtres Kitty/Firefox privées,
|
||||
reconnexion IPC et changements de résolution ;
|
||||
- navigation clavier du menu et présence de la règle SwayFX sans blur du dock.
|
||||
|
||||
Le dernier passage mesure 281 intervalles : moyenne 16.00 ms,
|
||||
95e percentile 16.46 ms, maximum 16.62 ms.
|
||||
Les intervalles d’animation sont conservés dans `result.json` ; ce sont des
|
||||
mesures de cadence Qt avec rendu logiciel, pas des timestamps de présentation
|
||||
matérielle. Ils établissent la stabilité du laboratoire privé. La perception
|
||||
sur le GPU, le fond d’écran et les sorties physiques de la session active reste
|
||||
à confirmer après intégration, sans prétendre l’avoir observée ici.
|
||||
|
||||
Captures issues de `grim`, sans retouche :
|
||||
|
||||
- [Dock fermé](validation/quickshell-dock-tela-polish-v2/closed.png).
|
||||
- [Dock ouvert](validation/quickshell-dock-tela-polish-v2/open.png).
|
||||
- [Dock survolé](validation/quickshell-dock-tela-polish-v2/zoom.png).
|
||||
- [Survol sur fond clair : ombre et transparence](validation/quickshell-dock-tela-polish-v2/zoom-light.png).
|
||||
|
||||
## Intégration
|
||||
|
||||
Le patch complet, fichiers nouveaux et PNG compris, se trouve à côté du worktree :
|
||||
`/home/fy59/.dotfiles-worktrees/quickshell-dock-tela-polish-v2.patch`.
|
||||
Les changements restent non commités et aucun fichier de favoris n’est livré.
|
||||
|
||||
Après revue, depuis le dépôt principal à la base compatible :
|
||||
|
||||
```sh
|
||||
cd /home/fy59/.dotfiles
|
||||
git apply --check /home/fy59/.dotfiles-worktrees/quickshell-dock-tela-polish-v2.patch
|
||||
git apply /home/fy59/.dotfiles-worktrees/quickshell-dock-tela-polish-v2.patch
|
||||
swaymsg reload
|
||||
systemctl --user restart quickshell-labfy-sway.service
|
||||
```
|
||||
|
||||
La vérification `git apply --check` est effectuée à la livraison. Les trois
|
||||
commandes d’application/rechargement restent à exécuter lors de l’intégration.
|
||||
Le redémarrage QuickShell recharge aussi les autres composants du shell.
|
||||
Après intégration, vérifier visuellement un balayage latéral, l’auto-hide et
|
||||
le menu sur les sorties physiques. Il n’est pas nécessaire de réécrire les
|
||||
favoris ou de redémarrer la session utilisateur.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
test_bad_state_does_not_get_overwritten (test_backend.FavoritesTests.test_bad_state_does_not_get_overwritten) ... ok
|
||||
test_seed_order_atomic_update_and_missing_entry (test_backend.FavoritesTests.test_seed_order_atomic_update_and_missing_entry) ... ok
|
||||
test_graphical_uses_desktop_id_and_uwsm_service (test_backend.LaunchTests.test_graphical_uses_desktop_id_and_uwsm_service) ... ok
|
||||
test_invalid_id_and_missing_entry (test_backend.LaunchTests.test_invalid_id_and_missing_entry) ... ok
|
||||
test_nodisplay_entry_is_refused_before_launch (test_backend.LaunchTests.test_nodisplay_entry_is_refused_before_launch) ... ok
|
||||
test_terminal_entry_already_running_kitty_is_not_wrapped_twice (test_backend.LaunchTests.test_terminal_entry_already_running_kitty_is_not_wrapped_twice) ... ok
|
||||
test_terminal_preserves_desktop_exec_fields_and_kitty (test_backend.LaunchTests.test_terminal_preserves_desktop_exec_fields_and_kitty) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.004s
|
||||
|
||||
OK
|
||||
BIN
docs/validation/quickshell-dock-tela-polish-v2/closed.png
Normal file
BIN
docs/validation/quickshell-dock-tela-polish-v2/closed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
18
docs/validation/quickshell-dock-tela-polish-v2/compat.log
Normal file
18
docs/validation/quickshell-dock-tela-polish-v2/compat.log
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/tmp/labfy-session-v2-TEST_ONLY-s23v41ci
|
||||
ready PASS
|
||||
accept PASS
|
||||
cancel PASS
|
||||
timeout PASS
|
||||
absent PASS
|
||||
still-locked PASS
|
||||
different PASS
|
||||
quickshell-restart-during-unlock PASS
|
||||
keyring_ui WITH_DOCK PASS
|
||||
success PASS
|
||||
failure PASS
|
||||
invalid PASS
|
||||
new PASS
|
||||
claimed PASS
|
||||
real PASS
|
||||
deferred-real PASS
|
||||
phase3b_ui WITH_DOCK PASS
|
||||
|
|
@ -0,0 +1 @@
|
|||
Dock headless: PASS /tmp/labfy-session-v2-TEST_ONLY-dlggtemr
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Dock state and window models: PASS
|
||||
ApplicationsModel: PASS
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
............................/usr/lib/python3.14/site-packages/gi/events.py:803: DeprecationWarning: 'asyncio.AbstractEventLoopPolicy' is deprecated and slated for removal in Python 3.16
|
||||
AbstractEventLoopPolicy = getattr(asyncio, "AbstractEventLoopPolicy", object)
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
../usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
./usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
../usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
../usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
..........................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 93 tests in 8.443s
|
||||
|
||||
OK
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.............................
|
||||
----------------------------------------------------------------------
|
||||
Ran 29 tests in 0.129s
|
||||
|
||||
OK
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/tmp/labfy-session-v2-TEST_ONLY-0p63m0if
|
||||
stable-final-focus PASS
|
||||
/tmp/labfy-session-v2-TEST_ONLY-vn3vcup_
|
||||
late-refused PASS
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/tmp/labfy-session-v2-TEST_ONLY-p4jqsmpt
|
||||
ready PASS
|
||||
accept PASS
|
||||
cancel PASS
|
||||
timeout PASS
|
||||
absent PASS
|
||||
still-locked PASS
|
||||
different PASS
|
||||
quickshell-restart-during-unlock PASS
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
guard-replacement PASS APPLICATION_MEMBERSHIP_CHANGED
|
||||
guard-extra PASS APPLICATION_MEMBERSHIP_CHANGED
|
||||
last-capture-layout PASS FINAL_TREE_MISMATCH
|
||||
last-capture-swap PASS RECOVERY_ORDER_CHANGED
|
||||
guard-finish-layout PASS FINAL_TREE_MISMATCH
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
pendant-guard SUCCÈS : quatre swaps, quatre slots, arbre exact, focus vérifié
|
||||
apres-phase0 SUCCÈS : quatre swaps, quatre slots, arbre exact, focus vérifié
|
||||
deux-frontieres SUCCÈS : quatre swaps, quatre slots, arbre exact, focus vérifié
|
||||
autotiling-guard SUCCÈS : quatre swaps, quatre slots, arbre exact, focus vérifié
|
||||
provider-unaire SUCCÈS : changement externe refusé avant IPC suivant
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
success PASS
|
||||
failure PASS
|
||||
invalid PASS
|
||||
new PASS
|
||||
claimed PASS
|
||||
real PASS
|
||||
deferred-real PASS
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
********* Start testing of qmltestrunner *********
|
||||
Config: Using QtTest library 6.12.0, Qt 6.12.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 16.2.1 20260810), arch unknown
|
||||
PASS : qmltestrunner::SessionExitGateV2::initTestCase()
|
||||
PASS : qmltestrunner::SessionExitGateV2::test_backend_reason_is_visible_and_logout_stays_gated()
|
||||
PASS : qmltestrunner::SessionExitGateV2::test_error_or_legacy_success_never_exits()
|
||||
PASS : qmltestrunner::SessionExitGateV2::test_quit_without_saving_requires_explicit_call()
|
||||
PASS : qmltestrunner::SessionExitGateV2::test_saved_releases_only_confirmed_action()
|
||||
PASS : qmltestrunner::SessionExitGateV2::cleanupTestCase()
|
||||
Totals: 6 passed, 0 failed, 0 skipped, 0 blacklisted, 5ms
|
||||
********* Finished testing of qmltestrunner *********
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 20.330s
|
||||
|
||||
OK
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"backend": 0,
|
||||
"focus": 0,
|
||||
"qml-unit": 0,
|
||||
"qml-qt": 0,
|
||||
"keyring-ui": 0,
|
||||
"keyring-runtime": 0,
|
||||
"scratchpad-runtime": 0,
|
||||
"phase3b-ui": 0,
|
||||
"p0-runtime": 0,
|
||||
"path-runtime": 0,
|
||||
"diff-check": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/tmp/labfy-session-v2-TEST_ONLY-1lfda8ji
|
||||
PASS save/store/restore/show/hide/new-membership/all-hidden/cold-kitty/no-duplicate
|
||||
12
docs/validation/quickshell-dock-tela-polish-v2/keyring.log
Normal file
12
docs/validation/quickshell-dock-tela-polish-v2/keyring.log
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/tmp/labfy-keyring-validation-ibq2bukh
|
||||
backend 0
|
||||
focus 0
|
||||
qml-unit 0
|
||||
qml-qt 0
|
||||
keyring-ui 0
|
||||
keyring-runtime 0
|
||||
scratchpad-runtime 0
|
||||
phase3b-ui 0
|
||||
p0-runtime 0
|
||||
path-runtime 0
|
||||
diff-check 0
|
||||
BIN
docs/validation/quickshell-dock-tela-polish-v2/open.png
Normal file
BIN
docs/validation/quickshell-dock-tela-polish-v2/open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
1637
docs/validation/quickshell-dock-tela-polish-v2/result.json
Normal file
1637
docs/validation/quickshell-dock-tela-polish-v2/result.json
Normal file
File diff suppressed because it is too large
Load diff
152
docs/validation/quickshell-dock-tela-polish-v2/session.log
Normal file
152
docs/validation/quickshell-dock-tela-polish-v2/session.log
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
test_absent_last_existing_directory (test_bootstrap.BootstrapTests.test_absent_last_existing_directory) ... ok
|
||||
test_absent_no_creation (test_bootstrap.BootstrapTests.test_absent_no_creation) ... ok
|
||||
test_allowlist_denied (test_bootstrap.BootstrapTests.test_allowlist_denied) ... ok
|
||||
test_checkpoint_created_between_observations (test_bootstrap.BootstrapTests.test_checkpoint_created_between_observations) ... ok
|
||||
test_empty_sessions_with_or_without_automatic_allows_without_creation (test_bootstrap.BootstrapTests.test_empty_sessions_with_or_without_automatic_allows_without_creation) ... ok
|
||||
test_envelope_and_framing_rejected (test_bootstrap.BootstrapTests.test_envelope_and_framing_rejected) ... ok
|
||||
test_home_fallback_matches_canonical_location (test_bootstrap.BootstrapTests.test_home_fallback_matches_canonical_location) ... ok
|
||||
test_interleaved_operations_map_unchanged (test_bootstrap.BootstrapTests.test_interleaved_operations_map_unchanged) ... ok
|
||||
test_last_with_sessions_absent_refuses (test_bootstrap.BootstrapTests.test_last_with_sessions_absent_refuses) ... ok
|
||||
test_named_only_without_automatic_refuses (test_bootstrap.BootstrapTests.test_named_only_without_automatic_refuses) ... ok
|
||||
test_os_error_is_not_absence (test_bootstrap.BootstrapTests.test_os_error_is_not_absence) ... ok
|
||||
test_present_and_corrupt_always_refuse (test_bootstrap.BootstrapTests.test_present_and_corrupt_always_refuse) ... ok
|
||||
test_sessions_any_entry_refuses_without_content_read (test_bootstrap.BootstrapTests.test_sessions_any_entry_refuses_without_content_read) ... ok
|
||||
test_sessions_list_error_refuses (test_bootstrap.BootstrapTests.test_sessions_list_error_refuses) ... ok
|
||||
test_sessions_unsafe_directory_refuses (test_bootstrap.BootstrapTests.test_sessions_unsafe_directory_refuses) ... ok
|
||||
test_socket_authority_failure_refuses_absence (test_bootstrap.BootstrapTests.test_socket_authority_failure_refuses_absence) ... ok
|
||||
test_state_symlink_and_relative_state_refuse (test_bootstrap.BootstrapTests.test_state_symlink_and_relative_state_refuse) ... ok
|
||||
test_symlinks_and_special_last_refuse_without_open (test_bootstrap.BootstrapTests.test_symlinks_and_special_last_refuse_without_open) ... ok
|
||||
test_unreadable_and_nonprivate_directory_refuse (test_bootstrap.BootstrapTests.test_unreadable_and_nonprivate_directory_refuse) ... ok
|
||||
test_valid_store_checkpoint_and_unreadable_file_refuse (test_bootstrap.BootstrapTests.test_valid_store_checkpoint_and_unreadable_file_refuse) ... ok
|
||||
test_equivalent_unary_paths_are_not_identity_changes (test_focus_recovery.FocusRecoveryTests.test_equivalent_unary_paths_are_not_identity_changes) ... ok
|
||||
test_firefox_command_proven_then_kitty_late_conflict_is_preserved (test_focus_recovery.FocusRecoveryTests.test_firefox_command_proven_then_kitty_late_conflict_is_preserved) ... ok
|
||||
test_interrupted_intention_preserves_all_evidence (test_focus_recovery.FocusRecoveryTests.test_interrupted_intention_preserves_all_evidence) ... ok
|
||||
test_layout_change_with_identical_raw_paths_still_refuses (test_focus_recovery.FocusRecoveryTests.test_layout_change_with_identical_raw_paths_still_refuses) ... ok
|
||||
test_path_evidence_limit_refuses_without_overwriting_history (test_focus_recovery.FocusRecoveryTests.test_path_evidence_limit_refuses_without_overwriting_history) ... ok
|
||||
test_real_identity_order_workspace_and_branch_changes_still_refuse (test_focus_recovery.FocusRecoveryTests.test_real_identity_order_workspace_and_branch_changes_still_refuse) ... ok
|
||||
test_prepare_quits_before_resume_and_finish_has_no_delayed_exit (test_guard_protocol.GuardProtocolTests.test_prepare_quits_before_resume_and_finish_has_no_delayed_exit) ... ok
|
||||
test_resume_without_prepare_refuses_and_still_cleans_owned_helper (test_guard_protocol.GuardProtocolTests.test_resume_without_prepare_refuses_and_still_cleans_owned_helper) ... ok
|
||||
test_actual_dbus_states (test_keyring.KeyringTests.test_actual_dbus_states) ... /usr/lib/python3.14/site-packages/gi/events.py:803: DeprecationWarning: 'asyncio.AbstractEventLoopPolicy' is deprecated and slated for removal in Python 3.16
|
||||
AbstractEventLoopPolicy = getattr(asyncio, "AbstractEventLoopPolicy", object)
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
ok
|
||||
test_apply_is_excluded_during_unlock (test_keyring.KeyringTests.test_apply_is_excluded_during_unlock) ... ok
|
||||
test_apply_last_cannot_bypass_gate (test_keyring.KeyringTests.test_apply_last_cannot_bypass_gate) ... /usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
ok
|
||||
test_ready_preserves_executor_result_and_recovery_required (test_keyring.KeyringTests.test_ready_preserves_executor_result_and_recovery_required) ... /usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
ok
|
||||
test_remote_error_text_is_never_logged_or_persisted (test_keyring.KeyringTests.test_remote_error_text_is_never_logged_or_persisted) ... ok
|
||||
test_transaction_prevents_prompt (test_keyring.KeyringTests.test_transaction_prevents_prompt) ... /usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
ok
|
||||
test_two_processes_and_restart (test_keyring.KeyringTests.test_two_processes_and_restart) ... ok
|
||||
test_unlock_never_restores (test_keyring.KeyringTests.test_unlock_never_restores) ... /usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
/usr/lib/python3.14/site-packages/gi/events.py:890: DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16
|
||||
if not isinstance(_get_event_loop_policy(), GLibEventLoopPolicy):
|
||||
ok
|
||||
test_complete_refuses_full_identity_placement_and_pending (test_p0_boundaries.BoundaryTests.test_complete_refuses_full_identity_placement_and_pending) ... ok
|
||||
test_extra_after_first_move_refuses_before_second_staging (test_p0_boundaries.BoundaryTests.test_extra_after_first_move_refuses_before_second_staging) ... ok
|
||||
test_guard_replacement_and_extra_refuse_before_any_mutation (test_p0_boundaries.BoundaryTests.test_guard_replacement_and_extra_refuse_before_any_mutation) ... ok
|
||||
test_last_provider_capture_cannot_hide_structure_or_identity_change (test_p0_boundaries.BoundaryTests.test_last_provider_capture_cannot_hide_structure_or_identity_change) ... ok
|
||||
test_layout_change_in_second_split_focus_observation_refuses (test_p0_boundaries.BoundaryTests.test_layout_change_in_second_split_focus_observation_refuses) ... ok
|
||||
test_unary_parent_normalization_preserves_canonical_layout (test_p0_boundaries.BoundaryTests.test_unary_parent_normalization_preserves_canonical_layout) ... ok
|
||||
test_unchanged_final_observation_completes (test_p0_boundaries.BoundaryTests.test_unchanged_final_observation_completes) ... ok
|
||||
test_changement_apres_capture_canonique_refuse_avant_deplacement (test_path_stability.PathTests.test_changement_apres_capture_canonique_refuse_avant_deplacement) ... ok
|
||||
test_guard_refuse_les_divergences_avant_tout_ipc (test_path_stability.PathTests.test_guard_refuse_les_divergences_avant_tout_ipc) ... ok
|
||||
test_ack_requires_verified_current_transaction (test_phase3b.DeploymentTests.test_ack_requires_verified_current_transaction) ... ok
|
||||
test_limusic_never_launched (test_phase3b.DeploymentTests.test_limusic_never_launched) ... ok
|
||||
test_async_mapping_completes_before_apply_and_focus_proof (test_prewarm.PrewarmTests.test_async_mapping_completes_before_apply_and_focus_proof) ... ok
|
||||
test_deferred_marker_and_cli_preserve_cause_for_explicit_retry (test_prewarm.PrewarmTests.test_deferred_marker_and_cli_preserve_cause_for_explicit_retry) ... ok
|
||||
test_existing_required_journal_refuses_before_launch (test_prewarm.PrewarmTests.test_existing_required_journal_refuses_before_launch) ... ok
|
||||
test_focus_change_after_proof_refuses_without_publication (test_prewarm.PrewarmTests.test_focus_change_after_proof_refuses_without_publication) ... ok
|
||||
test_intruder_extra_identity_session_and_owner_refuse_without_publication (test_prewarm.PrewarmTests.test_intruder_extra_identity_session_and_owner_refuse_without_publication) ... ok
|
||||
test_launch_failure_preserves_clients_and_retry_does_not_duplicate (test_prewarm.PrewarmTests.test_launch_failure_preserves_clients_and_retry_does_not_duplicate) ... ok
|
||||
test_missing_managed_autostart_refuses_before_launch (test_prewarm.PrewarmTests.test_missing_managed_autostart_refuses_before_launch) ... ok
|
||||
test_missing_collision_and_live_publisher (test_provider.ProviderTests.test_missing_collision_and_live_publisher) ... ok
|
||||
test_capture_is_deterministic_for_a_stable_tree (test_scratchpad.ScratchpadTests.test_capture_is_deterministic_for_a_stable_tree) ... ok
|
||||
test_capture_store_reload_and_no_internal_workspace (test_scratchpad.ScratchpadTests.test_capture_store_reload_and_no_internal_workspace) ... ok
|
||||
test_change_between_reads_and_ambiguous_focus_refuse (test_scratchpad.ScratchpadTests.test_change_between_reads_and_ambiguous_focus_refuse) ... ok
|
||||
test_empty_focus_workspace_is_persisted (test_scratchpad.ScratchpadTests.test_empty_focus_workspace_is_persisted) ... ok
|
||||
test_group_ancestor_is_not_admitted_by_leaf_identity (test_scratchpad.ScratchpadTests.test_group_ancestor_is_not_admitted_by_leaf_identity) ... ok
|
||||
test_hidden_focus_is_rejected_before_command (test_scratchpad.ScratchpadTests.test_hidden_focus_is_rejected_before_command) ... ok
|
||||
test_initial_scratchpad_add_predicts_source_workspace_focus (test_scratchpad.ScratchpadTests.test_initial_scratchpad_add_predicts_source_workspace_focus) ... ok
|
||||
test_journal_detects_identity_output_scratchpad_and_interruption (test_scratchpad.ScratchpadTests.test_journal_detects_identity_output_scratchpad_and_interruption) ... ok
|
||||
test_layer_shell_can_leave_no_sway_focus (test_scratchpad.ScratchpadTests.test_layer_shell_can_leave_no_sway_focus) ... ok
|
||||
test_legacy_journal_not_reinterpreted (test_scratchpad.ScratchpadTests.test_legacy_journal_not_reinterpreted) ... ok
|
||||
test_multiple_focused_nodes_are_still_refused (test_scratchpad.ScratchpadTests.test_multiple_focused_nodes_are_still_refused) ... ok
|
||||
test_runtime_visibility_assignment_never_exposes_hidden_peer (test_scratchpad.ScratchpadTests.test_runtime_visibility_assignment_never_exposes_hidden_peer) ... ok
|
||||
test_shown_and_multiple_hidden (test_scratchpad.ScratchpadTests.test_shown_and_multiple_hidden) ... ok
|
||||
test_strict_versions_and_scratchpad_references (test_scratchpad.ScratchpadTests.test_strict_versions_and_scratchpad_references) ... ok
|
||||
test_checkpoint_failed_validation_preserves_old (test_unit.BackendTests.test_checkpoint_failed_validation_preserves_old) ... ok
|
||||
test_checkpoint_permissions (test_unit.BackendTests.test_checkpoint_permissions) ... ok
|
||||
test_cli_execute_required (test_unit.BackendTests.test_cli_execute_required) ... ok
|
||||
test_cli_storage_checkpoint_and_startup_mock (test_unit.BackendTests.test_cli_storage_checkpoint_and_startup_mock) ... ok
|
||||
test_coexistence_and_migration (test_unit.BackendTests.test_coexistence_and_migration) ... ok
|
||||
test_command_injection (test_unit.BackendTests.test_command_injection) ... ok
|
||||
test_counts (test_unit.BackendTests.test_counts) ... ok
|
||||
test_exact_inverted (test_unit.BackendTests.test_exact_inverted) ... ok
|
||||
test_interchangeable_best_effort (test_unit.BackendTests.test_interchangeable_best_effort) ... ok
|
||||
test_intermediate_directory_permissions (test_unit.BackendTests.test_intermediate_directory_permissions) ... ok
|
||||
test_json_schema_matches_fixture (test_unit.BackendTests.test_json_schema_matches_fixture) ... ok
|
||||
test_json_strict (test_unit.BackendTests.test_json_strict) ... ok
|
||||
test_launch_policy (test_unit.BackendTests.test_launch_policy) ... ok
|
||||
test_lock (test_unit.BackendTests.test_lock) ... ok
|
||||
test_multi_output_preflight_synthetic (test_unit.BackendTests.test_multi_output_preflight_synthetic) ... ok
|
||||
test_native_strict (test_unit.BackendTests.test_native_strict) ... ok
|
||||
test_paths_links (test_unit.BackendTests.test_paths_links) ... ok
|
||||
test_plan_pure (test_unit.BackendTests.test_plan_pure) ... ok
|
||||
test_references (test_unit.BackendTests.test_references) ... ok
|
||||
test_report_rejects_private_fields_and_bool_version (test_unit.BackendTests.test_report_rejects_private_fields_and_bool_version) ... ok
|
||||
test_resume_attempts_all_owned_helpers (test_unit.BackendTests.test_resume_attempts_all_owned_helpers) ... ok
|
||||
test_schema_exact (test_unit.BackendTests.test_schema_exact) ... ok
|
||||
test_types_version_and_identity (test_unit.BackendTests.test_types_version_and_identity) ... ok
|
||||
test_unsupported_states (test_unit.BackendTests.test_unsupported_states) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 93 tests in 8.405s
|
||||
|
||||
OK
|
||||
1
docs/validation/quickshell-dock-tela-polish-v2/shell.log
Normal file
1
docs/validation/quickshell-dock-tela-polish-v2/shell.log
Normal file
|
|
@ -0,0 +1 @@
|
|||
Full shell private-compositor load: PASS /tmp/labfy-session-v2-TEST_ONLY-vx5tlv5i
|
||||
11
docs/validation/quickshell-dock-tela-polish-v2/unit.log
Normal file
11
docs/validation/quickshell-dock-tela-polish-v2/unit.log
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
test_inheritance_from_a_private_test_theme (test_shared.Icons.test_inheritance_from_a_private_test_theme) ... ok
|
||||
test_tela_desktop_icon_and_absolute (test_shared.Icons.test_tela_desktop_icon_and_absolute) ... ok
|
||||
test_nine_move_missing_anchor_and_empty (test_shared.Persistence.test_nine_move_missing_anchor_and_empty) ... ok
|
||||
test_real_concurrent_processes_keep_all_additions_during_moves (test_shared.Persistence.test_real_concurrent_processes_keep_all_additions_during_moves) ... ok
|
||||
test_toggle_is_atomic_and_does_not_depend_on_a_stale_ui_list (test_shared.Persistence.test_toggle_is_atomic_and_does_not_depend_on_a_stale_ui_list) ... ok
|
||||
test_wayland_xwayland_scratchpad_and_explicit_focus (test_shared.Sway.test_wayland_xwayland_scratchpad_and_explicit_focus) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.735s
|
||||
|
||||
OK
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"status": "PASS",
|
||||
"base": "d4e2a0013e7a9452add80dddc110e036ebe4ee96",
|
||||
"backendTests": {
|
||||
"applications": 7,
|
||||
"dock": 6,
|
||||
"sessionV2": 93
|
||||
},
|
||||
"javascript": [
|
||||
"ApplicationsModel: PASS",
|
||||
"Dock state and window models: PASS"
|
||||
],
|
||||
"graphical": [
|
||||
"private Sway dock scenario",
|
||||
"complete shell load",
|
||||
"Session V2 and Keyring with dock"
|
||||
],
|
||||
"keyring": {
|
||||
"backend": 0,
|
||||
"focus": 0,
|
||||
"qml-unit": 0,
|
||||
"qml-qt": 0,
|
||||
"keyring-ui": 0,
|
||||
"keyring-runtime": 0,
|
||||
"scratchpad-runtime": 0,
|
||||
"phase3b-ui": 0,
|
||||
"p0-runtime": 0,
|
||||
"path-runtime": 0,
|
||||
"diff-check": 0
|
||||
},
|
||||
"finalChecks": [
|
||||
"git diff --check: PASS",
|
||||
"git apply --check against unchanged main checkout: PASS",
|
||||
"no Binding loop, assignment, ReferenceError or TypeError in final dock lab logs",
|
||||
"main checkout git status --short: empty"
|
||||
],
|
||||
"finalShellLab": "/tmp/labfy-session-v2-TEST_ONLY-ywdppzn5",
|
||||
"activeSessionModified": false
|
||||
}
|
||||
BIN
docs/validation/quickshell-dock-tela-polish-v2/zoom-light.png
Normal file
BIN
docs/validation/quickshell-dock-tela-polish-v2/zoom-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/validation/quickshell-dock-tela-polish-v2/zoom.png
Normal file
BIN
docs/validation/quickshell-dock-tela-polish-v2/zoom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -17,5 +17,9 @@ function next(state, event, input) {
|
|||
}
|
||||
|
||||
function influence(distance) {
|
||||
return Math.max(0, 1 - Math.abs(distance) / 115);
|
||||
// CONTRACT: 57 px resting pitch; a radius below 1.5 pitches affects
|
||||
// at most the nearest slot and its immediate neighbours. Smoothstep has
|
||||
// zero slope at both ends, avoiding a kink as an icon enters the halo.
|
||||
const t = Math.max(0, 1 - Math.abs(distance) / 76);
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "../applications"
|
||||
|
|
@ -21,7 +22,7 @@ PanelWindow {
|
|||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "labfy-sway-dock"
|
||||
color: "transparent"
|
||||
implicitWidth: Math.max(96, Math.min(screen.width - 24, items.width + 28))
|
||||
implicitWidth: Math.max(96, Math.min(screen.width - 24, items.width + 52))
|
||||
implicitHeight: 360
|
||||
property string phase: "hidden"
|
||||
readonly property bool revealed: phase === "shown" || phase === "waiting" || phase === "held"
|
||||
|
|
@ -96,27 +97,33 @@ PanelWindow {
|
|||
onHoveredChanged: { dock.reconcile(); if (!hovered) dock.pointerX = -1000; }
|
||||
onPointChanged: dock.pointerX = point.position.x
|
||||
}
|
||||
Rectangle {
|
||||
// Discreet local shadow, no screen capture or blur sampling.
|
||||
x: 2; y: 34; width: parent.width - 4; height: 70
|
||||
radius: 26; color: Theme.shadow
|
||||
// WHY: an analytic shadow needs neither a captured backdrop nor a
|
||||
// moving blur source. Its geometry stays fixed throughout hover.
|
||||
RectangularShadow {
|
||||
x: 16; y: 43; width: parent.width - 32; height: 48
|
||||
radius: 20; blur: 24; spread: 2
|
||||
color: Qt.rgba(0, 0, 0, 0.32)
|
||||
}
|
||||
Rectangle {
|
||||
x: 0; y: 29; width: parent.width; height: 70
|
||||
radius: 25
|
||||
color: Qt.rgba(Theme.base.r, Theme.base.g, Theme.base.b, Theme.highContrast ? 1 : 0.89)
|
||||
border.color: Theme.strongBorder; border.width: 1
|
||||
x: 14; y: 36; width: parent.width - 28; height: 58
|
||||
radius: 20
|
||||
color: Qt.rgba(Theme.base.r, Theme.base.g, Theme.base.b, Theme.highContrast ? 0.94 : 0.16)
|
||||
border.color: Qt.rgba(Theme.text.r, Theme.text.g, Theme.text.b, Theme.highContrast ? 0.5 : 0.09)
|
||||
border.width: 1
|
||||
}
|
||||
Flickable {
|
||||
id: viewport
|
||||
x: 12; y: 0; width: parent.width - 24; height: 105
|
||||
contentWidth: items.width; contentHeight: height
|
||||
// CONTRACT: reserve visual overflow inside the clipped viewport,
|
||||
// including at both scroll limits, for the 1.39x end icons.
|
||||
contentWidth: items.width + 24; contentHeight: height
|
||||
clip: true
|
||||
interactive: dock.draggingId === ""
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
Row {
|
||||
id: items
|
||||
x: 12
|
||||
height: viewport.height
|
||||
spacing: 3
|
||||
move: Transition { NumberAnimation { properties: "x"; duration: 110; easing.type: Easing.OutCubic } }
|
||||
|
|
@ -128,29 +135,32 @@ PanelWindow {
|
|||
required property var modelData
|
||||
required property int index
|
||||
readonly property bool separator: index === dock.pinnedCount && dock.pinnedCount > 0
|
||||
readonly property real center: viewport.x + x + width / 2 - viewport.contentX
|
||||
// WHY: measuring against the animated x/width feeds
|
||||
// scale back into its own binding. Stable resting slots
|
||||
// keep magnification deterministic and free of loops.
|
||||
readonly property real baseCenter: viewport.x + index * 57 + 27
|
||||
readonly property real center: viewport.x + items.x + x + width / 2 - viewport.contentX
|
||||
// INVARIANT: pointer influence uses fixed layout slots.
|
||||
// Only the visual transform changes: Row, hit targets,
|
||||
// window width and SVG raster size never follow hover.
|
||||
readonly property real baseCenter: viewport.x + items.x + index * 57 + 27
|
||||
+ (index >= dock.pinnedCount && dock.pinnedCount > 0 ? 13 : 0) - viewport.contentX
|
||||
readonly property real growth: dock.revealed && bodyHover.hovered
|
||||
? AutoHide.influence(baseCenter - dock.pointerX) : 0
|
||||
width: 54 + growth * 19 + (separator ? 13 : 0)
|
||||
width: 54 + (separator ? 13 : 0)
|
||||
height: 105
|
||||
Behavior on width { NumberAnimation { duration: 95; easing.type: Easing.OutCubic } }
|
||||
Rectangle {
|
||||
visible: tile.separator
|
||||
x: 2; y: 45; width: 1; height: 32; color: Theme.strongBorder
|
||||
}
|
||||
ApplicationIcon {
|
||||
id: icon
|
||||
width: 46 + tile.growth * 21; height: width
|
||||
// WHY: ApplicationIcon derives sourceSize from width.
|
||||
// Fixed dimensions avoid async SVG reload/fallback flashes.
|
||||
width: 46; height: 46
|
||||
x: (parent.width - width + (tile.separator ? 12 : 0)) / 2
|
||||
y: 83 - height
|
||||
y: 37
|
||||
transformOrigin: Item.Bottom
|
||||
scale: 1 + tile.growth * 0.39
|
||||
Behavior on scale { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } }
|
||||
iconSource: tile.modelData.iconSource || ""
|
||||
opacity: dock.draggingId === tile.modelData.id ? 0.5 : 1
|
||||
Behavior on width { NumberAnimation { duration: 95; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: icon.horizontalCenter
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ layer_effects "labfy-sway-bar" {
|
|||
corner_radius 4
|
||||
}
|
||||
|
||||
# CONTRACT: only the dock's layer surface receives blur; no window rule or
|
||||
# focus/restore policy is changed. Its transparent input strip stays clear.
|
||||
# CONTRACT: dock glass uses a light tint and analytic QML shadow. Disabling
|
||||
# backdrop blur avoids compositor blur work on every magnification frame.
|
||||
# No window rule or focus/restore policy is changed.
|
||||
layer_effects "labfy-sway-dock" {
|
||||
blur $bar_blur
|
||||
blur disable
|
||||
blur_xray disable
|
||||
blur_ignore_transparent enable
|
||||
shadows disable
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from headless import Lab
|
|||
from ipc_proxy import IpcProxy
|
||||
|
||||
SOURCE = REPO / 'quickshell/.config/quickshell/labfy-sway'
|
||||
ARTIFACTS = REPO / 'docs/validation/quickshell-dock-tela'
|
||||
ARTIFACTS = REPO / 'docs/validation/quickshell-dock-tela-polish-v2'
|
||||
NINE = ['firefox.desktop', 'kitty.desktop', 'yazi.desktop', 'nvim.desktop',
|
||||
'org.darktable.darktable.desktop', 'orca-slicer.desktop', 'org.freecad.FreeCAD.desktop',
|
||||
'org.kde.digikam.desktop', 'AppFlowy.desktop']
|
||||
|
|
@ -23,8 +23,8 @@ class DockLab(Lab):
|
|||
super().__init__()
|
||||
self.config=self.directory/'dock-sway.conf'
|
||||
base=(REPO/'tests/session_v2/fixtures/headless.conf').read_text()
|
||||
effects=(REPO/'sway/.config/sway/swayfx').read_text().split('# CONTRACT: only the dock')[1]
|
||||
effects=effects[effects.index('layer_effects'):]
|
||||
effects=(REPO/'sway/.config/sway/swayfx').read_text().split('layer_effects "labfy-sway-dock"')[1]
|
||||
effects='layer_effects "labfy-sway-dock"'+effects
|
||||
self.config.write_text(base+'\noutput HEADLESS-1 bg #11111b solid_color\nset $bar_blur enable\n'+effects)
|
||||
def start(self, argv):
|
||||
if argv[0]==os.environ.get('LABFY_TEST_SWAY','sway'):
|
||||
|
|
@ -49,6 +49,31 @@ ShellRoot {
|
|||
D.Dock { id: dock; modelData: Quickshell.screens[0] }
|
||||
PanelWindow { id: bar; implicitWidth: 100; implicitHeight: 30; anchors.top: true; color: "transparent" }
|
||||
LazyLoader { id: menu; active: false; A.ApplicationsMenu { barWindow: bar } }
|
||||
// TEST_ONLY: one pointer move per animation frame avoids measuring IPC
|
||||
// process startup instead of continuous production hover. These are Qt
|
||||
// animation intervals, not hardware presentation timestamps.
|
||||
FrameAnimation {
|
||||
id: sweepFrames
|
||||
property var intervals: []
|
||||
property real origin: 0
|
||||
property bool geometryChanged: false
|
||||
property bool imageChanged: false
|
||||
property var baseline: []
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (currentFrame > 1) intervals.push(frameTime * 1000);
|
||||
const cycle = (elapsedTime * 0.7) % 2;
|
||||
input.mouseMove(dock.surfaceBody, 51 + 420 * (cycle <= 1 ? cycle : 2 - cycle), 70);
|
||||
const tiles=Array.from(dock.contentItemRow.children).filter(t => t.modelData);
|
||||
for (let i=0; i<tiles.length; ++i) {
|
||||
const t=tiles[i], b=baseline[i];
|
||||
if (t.x!==b.x || t.width!==b.width || t.children[1].width!==46 || dock.width!==origin)
|
||||
geometryChanged=true;
|
||||
if (t.children[1].children[0].status!==b.status) imageChanged=true;
|
||||
}
|
||||
if (elapsedTime >= 4.5) running=false;
|
||||
}
|
||||
}
|
||||
TestCase { id: input; name: "DockInput"; when: false }
|
||||
IpcHandler {
|
||||
target: "dockTest"
|
||||
|
|
@ -64,10 +89,30 @@ ShellRoot {
|
|||
bodyY:dock.surfaceBody.y, width:dock.width, pointerX:dock.pointerX,
|
||||
|
||||
tileWidth:dock.contentItemRow.children[0] ? dock.contentItemRow.children[0].width : 0,
|
||||
tiles:Array.from(dock.contentItemRow.children).filter(t => t.modelData).map(t => ({
|
||||
x:t.x, width:t.width, growth:t.growth, scale:t.children[1].scale,
|
||||
visualLeft:t.children[1].mapToItem(dock.contentItemRow.parent.parent,0,0).x,
|
||||
visualRight:t.children[1].mapToItem(dock.contentItemRow.parent.parent,46,0).x,
|
||||
iconWidth:t.children[1].width, imageStatus:t.children[1].children[0].status})),
|
||||
context:!!dock.contextEntry});
|
||||
}
|
||||
function startFrames(): void {
|
||||
sweepFrames.intervals=[]; sweepFrames.origin=dock.width;
|
||||
sweepFrames.baseline=Array.from(dock.contentItemRow.children).filter(t => t.modelData)
|
||||
.map(t => ({x:t.x,width:t.width,status:t.children[1].children[0].status}));
|
||||
sweepFrames.start();
|
||||
}
|
||||
function frames(): string {
|
||||
return JSON.stringify({running:sweepFrames.running, intervals:sweepFrames.intervals,
|
||||
geometryChanged:sweepFrames.geometryChanged,imageChanged:sweepFrames.imageChanged});
|
||||
}
|
||||
function edge(): void { input.mouseMove(dock.edgeArea, dock.edgeArea.width/2, 2); }
|
||||
function body(): void { input.mouseMove(dock.surfaceBody, 35, 70); }
|
||||
function sweep(x:real): void { input.mouseMove(dock.surfaceBody, x, 70); }
|
||||
function scrollEnd(): void {
|
||||
const v=dock.contentItemRow.parent.parent; v.contentX=Math.max(0,v.contentWidth-v.width);
|
||||
}
|
||||
function scrollStart(): void { dock.contentItemRow.parent.parent.contentX=0; }
|
||||
function leave(): void { input.mouseMove(dock.contentItem, 1, 1); }
|
||||
function menuToggle(id:string): void { if (!menu.active) menu.active=true; menu.item.toggleFavorite(id); }
|
||||
function dockToggle(id:string): void { A.FavoritesService.toggle(id); }
|
||||
|
|
@ -120,8 +165,33 @@ ShellRoot {
|
|||
capture('closed')
|
||||
call('edge'); wait(lambda s:s['revealed']); time.sleep(.35)
|
||||
capture('open')
|
||||
resting=call('state')
|
||||
call('body'); time.sleep(.3); zoom=call('state'); capture('zoom')
|
||||
assert zoom['tileWidth']>54, zoom
|
||||
lab.command('output HEADLESS-1 bg #aab4c8 solid_color')
|
||||
time.sleep(.2); capture('zoom-light')
|
||||
lab.command('output HEADLESS-1 bg #11111b solid_color')
|
||||
assert zoom['tileWidth']==54 and zoom['tiles'][0]['scale']>1, zoom
|
||||
assert zoom['tiles'][0]['visualLeft']>=0,zoom
|
||||
# Exercise production hover across slot boundaries. Layout and image
|
||||
# readiness must remain stable while visual transforms change.
|
||||
samples=[]
|
||||
for x in range(45, 480, 6):
|
||||
call('sweep',x)
|
||||
state=call('state')
|
||||
assert state['phase']=='shown',state
|
||||
assert state['width']==resting['width'],state
|
||||
assert [(t['x'],t['width'],t['iconWidth']) for t in state['tiles']]==[(t['x'],t['width'],t['iconWidth']) for t in resting['tiles']],state
|
||||
assert sum(t['growth']>0 for t in state['tiles'])<=3,state
|
||||
assert [t['imageStatus'] for t in state['tiles']]==[t['imageStatus'] for t in resting['tiles']],state
|
||||
samples.append({'pointerX':state['pointerX'],'scales':[t['scale'] for t in state['tiles']]})
|
||||
call('startFrames')
|
||||
time.sleep(4.8)
|
||||
frames=call('frames')
|
||||
assert not frames['running'] and len(frames['intervals'])>30,frames
|
||||
assert not frames['geometryChanged'] and not frames['imageChanged'],frames
|
||||
intervals=sorted(frames['intervals'])
|
||||
timing={'frames':len(intervals),'meanMs':sum(intervals)/len(intervals),
|
||||
'p95Ms':intervals[int((len(intervals)-1)*.95)],'maxMs':max(intervals)}
|
||||
call('leave'); wait(lambda s:s['phase']=='hidden')
|
||||
call('edge'); wait(lambda s:s['revealed']); time.sleep(.3)
|
||||
call('rightClick'); wait(lambda s:s['phase']=='held'); time.sleep(.7)
|
||||
|
|
@ -199,6 +269,16 @@ ShellRoot {
|
|||
lab.command('output HEADLESS-1 mode 800x600')
|
||||
resized=wait(lambda s:s['screenWidth']==800)
|
||||
assert resized['width']<=776,resized
|
||||
# Magnified end icons keep their visual margin at both scroll limits.
|
||||
lab.command('output HEADLESS-1 mode 400x600')
|
||||
wait(lambda s:s['screenWidth']==400)
|
||||
call('edge'); wait(lambda s:s['revealed']); time.sleep(.3)
|
||||
call('scrollEnd'); call('sweep',325); time.sleep(.15)
|
||||
end=call('state')
|
||||
assert end['tiles'][-1]['scale']>1 and end['tiles'][-1]['visualRight']<=end['width']-24,end
|
||||
call('scrollStart'); call('sweep',51); time.sleep(.15)
|
||||
start=call('state')
|
||||
assert start['tiles'][0]['scale']>1 and start['tiles'][0]['visualLeft']>=0,start
|
||||
lab.command('output HEADLESS-1 mode 1280x720')
|
||||
wait(lambda s:s['screenWidth']==1280)
|
||||
# Existing empty state must stay empty, including a bridge restart.
|
||||
|
|
@ -208,9 +288,9 @@ ShellRoot {
|
|||
process=lab.start(['quickshell','-p',str(qml)])
|
||||
time.sleep(1)
|
||||
assert wait(lambda s:s['ready'])['ids']==[]
|
||||
(ARTIFACTS/'result.json').write_text(json.dumps(dict(status='PASS',initial=initial,
|
||||
(ARTIFACTS/'result.json').write_text(json.dumps(dict(status='PASS',initial=initial,hoverSamples=samples,animationTiming=timing,frameSweep=frames,
|
||||
reordered=reordered['ids'],checks=['shared favorites','menu and dock mutations','durable reorder',
|
||||
'empty restart','autohide','context hold','kitty multi-window','floating scratchpad explicit focus',
|
||||
'window close','no hover focus','real pointer drag','zoom growth','IPC reconnect','Firefox private profile','desktop install/remove monitoring','external writer','keyboard menu navigation','resolution change','SwayFX dock blur rule']),indent=2)+'\n')
|
||||
'window close','no hover focus','real pointer drag','fixed layout transform hover', 'stable SVG readiness', 'lateral sweep 73 samples','IPC reconnect','Firefox private profile','desktop install/remove monitoring','external writer','keyboard menu navigation','resolution change','SwayFX dock blur disabled']),indent=2)+'\n')
|
||||
proxy.close()
|
||||
print('Dock headless: PASS',lab.directory)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,15 @@ input.held=false; assert.equal(event(),'waiting');
|
|||
assert.equal(event('deadline'),'hidden');
|
||||
input.held=true; event(); input.suppressed=true; assert.equal(event(),'hidden');
|
||||
assert.equal(hide.influence(0),1);
|
||||
assert.ok(hide.influence(55)>hide.influence(100));
|
||||
assert.equal(hide.influence(115),0);
|
||||
assert.ok(hide.influence(25)>hide.influence(55));
|
||||
assert.equal(hide.influence(76),0);
|
||||
assert.equal(hide.influence(-76),0);
|
||||
assert.equal(hide.influence(-35),hide.influence(35));
|
||||
for (let cursor=0; cursor<570; ++cursor) {
|
||||
const affected=Array.from({length:10},(_,i)=>i).filter(i=>hide.influence(i*57-cursor)>0);
|
||||
assert.ok(affected.length<=3);
|
||||
assert.ok(affected.length===0 || affected.at(-1)-affected[0]<=2);
|
||||
}
|
||||
const model=load('applications/WindowModel.js');
|
||||
const apps=[{id:'kitty.desktop',aliases:['kitty'],iconSource:'file:///tela/kitty.svg'},
|
||||
{id:'org.app.desktop',aliases:['startup-class','executable']},
|
||||
|
|
|
|||
Loading…
Reference in a new issue