ADR-009 — Framework de vues d’Air : tronc déclaratif air-view, bindings air-ui (graphique) et air-tui (texte)
Statut : draft de version révisée, en attente de revue BDFL. Remplace l’ADR-009 actuel du registre.
Note de révision : cette version intègre l’amendement déclaratif (anciennement envisagé sous le nom ADR-009-bis) directement dans le corps d’ADR-009, conformément à la politique d’édition en phase de design pré-ouverture publique.
Paradigme
Paradigme déclaratif/réactif en pure Rust, sans DSL séparé. Une macro view! optionnelle peut être fournie pour les cas de syntaxe condensée, mais l’API principale est composée d’appels de fonctions et de méthodes Rust standard. Verbosité assumée au profit de la clarté du data-flow et de la performance runtime, conformément au principe d’ingénierie 7.
API publique exclusivement déclarative
L’API exposée au développeur d’application n’expose aucune coordonnée absolue, ni en pixels, ni en cellules, ni en aucune autre unité physique. Le positionnement et le dimensionnement s’expriment exclusivement par :
- des conteneurs de mise en page déclaratifs :
HStack,VStack,ZStack,Grid,LazyStacket variantes, qui composent leurs enfants selon des règles déclaratives ; - des modificateurs déclaratifs :
padding,spacing,alignment,frame(en termes contraints),weight, etc. ; - des unités logiques :
.small,.medium,.largeet variantes proportionnelles, jamais en pixels ou cellules.
La typographie est exprimée comme rôle sémantique (.title, .body, .caption, .code, etc.), jamais comme triplet (famille, taille, graisse). Le renderer cible résout le rôle vers une police concrète selon son contexte : police par défaut système (Helvetica ou équivalent) sous air-ui-render-gpu, police monospace de la console sous air-ui-render-tui.
Ce choix est structurant et non négociable parce que la promesse « code dual-mode » de la Vision ne peut tenir que si les APIs publiques utilisées par le développeur ont une sémantique commune dans les deux modes. Les coordonnées absolues n’ont pas de sémantique commune entre les deux modes : le mode graphique opère sur un écran à DPI variable, le mode console opère sur une grille de cellules dont la taille pixel varie selon la police et le DPI (sur air-console) ou n’est même pas connue (sur terminal tiers selon ADR-008). L’arrondi naïf à la cellule n’est pas une réponse acceptable (chevauchements, gaps, casse sur texte non-ASCII type CJK/emoji).
Cette posture est par ailleurs la norme des frameworks UI modernes (SwiftUI, Xilem, Compose, Flutter), corrigeant l’erreur des SDKs UI antérieurs (Win32, AppKit pre-Auto Layout, GTK 2) qui exposaient des coordonnées absolues et obligeaient à des recalculs manuels pour chaque résolution cible.
Mécanismes de state
Trois mécanismes inspirés de SwiftUI mais transposés en idiome explicite :
State<T>: state local par vue, identifié par position d’appel surCx.Observable<T>: objets observables vivant dans le runtime C-ABI de la couche 2, abonnement explicite viacx.observe().Environment<T>: injection par contexte d’arbre.
State local par vue ; pas de state global imposé ; partage via Observable. Animation déclarative first-class.
Architecture des crates
-
air-object-view— substrat impératif vivant dans le runtime d’objets C-ABI de la couche 2. Vues élémentaires, contexte de composition, scene-graph abstrait, types primitifs partagés. C’est le bas niveau sur lequel tout repose. -
air-view— tronc déclaratif commun en couche 4. Expose les conteneurs de mise en page, les modificateurs, les mécanismes de state, l’APICapabilities. Consommable directement par les applications dual-mode qui n’ont besoin que du vocabulaire commun. -
air-ui— bindings idiomatiques + widget set graphique en couche 4. Embarqueair-viewetair-ui-render-gpu. Utilisé par les apps purement graphiques ou par les apps dual-mode qui veulent les widgets idiomatiques pour leur partie graphique. -
air-tui— bindings idiomatiques + widget set TUI en couche 4. Embarqueair-viewetair-ui-render-tui. Utilisé par les apps purement TUI ou par les apps dual-mode qui veulent les widgets idiomatiques pour leur partie texte. -
air-ui-render-gpu— renderer graphique via Vello/GPU surair-wm(cf. ADR-018 pour le modèle d’imagerie sous-jacent). -
air-ui-render-tui— renderer cellulaire surair-consoleou terminal selon les trois backends d’ADR-008. -
air-ui-controller— API intermédiaire pour les cas qui mélangent paradigmes déclaratif et impératif (par exemple intégration progressive d’un composant impératif legacy, ou besoin d’un contrôle fin du cycle de rendu).
Le partage du modèle de composition entre les deux renderers passe par air-view : un widget sémantique commun (Button, Text, List, TextField, Form, etc.) a une implémentation dans chaque renderer, et l’app développeur l’utilise sans savoir lequel des deux tournera.
Capability-gating des primitives mode-specific
Certaines fonctionnalités n’ont de sens que dans un mode. Elles existent dans les bindings idiomatiques air-ui et air-tui mais sont explicitement isolées dans des namespaces séparés (nommage à arrêter au moment de la spécification framework, candidats air_ui::gfx::* et air_tui::cells::*) et capability-gated : leur usage est détecté à la compilation ou au runtime comme un usage qui sort du périmètre dual-mode portable, et signalé clairement au développeur via l’API Capabilities (étendue de celle d’ADR-008).
Primitives spécifiques au mode graphique : gradients, shaders custom, blur, transformations 3D, animations sub-pixel, blending stack avancé.
Primitives spécifiques au mode texte : box-drawing characters, attributs cellulaires (blink, reverse, underline-style), modes alternatifs d’écran, hints de couleur ANSI 256.
Escape hatches pour taille exacte
Il existe des cas légitimes où un développeur veut une taille exacte : visualisation scientifique pixel-perfect, app de design graphique, jeu. Ces cas sont autorisés via des modificateurs explicitement marqués comme non-portables, par exemple .frameExact(width: 800, height: 600, unit: .pixels). L’utilisation de ces modificateurs déclenche le même capability-gating que les primitives mode-specific : l’app sort du périmètre dual-mode portable, le système le détecte et le signale.
Ces escape hatches sont conservés dans l’API standard plutôt que renvoyés à air-experimental parce que les cas d’usage légitimes existent vraiment et qu’on ne veut pas pousser les développeurs concernés à écrire leur propre couche en contournant le framework. Le capability-gating suffit à protéger l’invariant dual-mode pour les apps qui s’en revendiquent.
Inspirations techniques
Xilem, Iced, SwiftUI. L’architecture en trois étages (substrat C-ABI impératif air-object-view / tronc déclaratif commun air-view / bindings idiomatiques air-ui et air-tui) est inspirée de la stratification Xilem mais transposée à la dualité graphique/texte propre à Air et à la contrainte C-ABI propre à la couche 2.
Cross-références
- ADR-002 (modèle d’objet C-ABI) —
air-object-viewvit dans ce runtime, lesObservable<T>sont desAirObject. - ADR-008 (
air-tuitrois backends) — laCapabilitiesAPI y est introduite et étendue ici pour couvrir le capability-gating des primitives mode-specific et des escape hatches. - ADR-017 (accessibilité) — l’API déclarative permet aussi l’accessibilité par construction et la compatibilité avec l’agrandisseur intégré ; le rejet des coordonnées absolues est une condition de l’accessibilité par zoom.
- ADR-018 (modèle d’imagerie) — vit sous cette API déclarative ; le développeur d’app ne voit ni les commandes vectorielles ni les commandes cellulaires.
- Principe d’ingénierie 7 (verbosité au service de la clarté) — informe l’arbitrage en faveur du déclaratif explicite contre l’impératif laconique.
Licence du document : MPL 2.0 Statut : draft de révision en attente de validation BDFL. Une fois validé, ce texte remplace l’ADR-009 actuel dans le registre canonique.