ADR 0043 – Recherche à opérateurs dans la palette, et vue née d'une recherche
Accepté (2026-10-16, mode autonome ; lancé par le fil coordinateur pendant qu'Aurélien choisit la suite de la vague 2, il peut l'arrêter)
ADR 0043 – Recherche à opérateurs dans la palette, et vue née d’une recherche
Statut : Accepté (2026-10-16, mode autonome ; lancé par le fil coordinateur pendant qu’Aurélien choisit la suite de la vague 2, il peut l’arrêter)
La vague 2 propose une recherche à opérateurs dans la palette Ctrl+K, qui réutilise le langage where des vues : type:tache statut:a-faire #maison echeance<7j "texte exact" -exclu. Elle doit compléter les opérateurs et les valeurs, permettre « Enregistrer comme vue » (un YAML de vue écrit dans config/views/), et être documentée dans le site.
La même tranche termine l’ADR 0041 : on peut désormais cocher des éléments pour une action en lot aussi en liste et en kanban.
Décision
Analyse côté serveur, traduite en requête de vue
- Où.
SearchQuery(paquetdefinitions.query) ne fait que traduire : il n’évalue rien et produit une requête de vue (ADR 0028). Cette requête a trois parties : une conditionwhere, des tags et un texte. - Pourquoi côté serveur. Les définitions y sont, et le résultat passe par les mêmes contrôles qu’une vue de
config/(ExprChecker, puischeckView). Une recherche exécutée et une vue enregistrée ne peuvent donc pas diverger. - Grammaire.
type:xvise le type du nœud.champ:valeur(égalité),champ<,<=,>,>=et!=comparent ; un-en tête exclut.- Le champ peut être un champ d’aspect, écrit
aspect.champou seul, ou un champ du nœud (titre,cree,modifie). #tagexige un tag (et ses sous-tags, comme les vues).- Seul un nom de champ connu fait un opérateur :
Re: devis,TODO:ou une adresse web restent du texte. - Le reste, y compris
"texte exact"et-mot, forme le texte libre. - Les tags sont mis sous forme canonique et dédoublonnés (au plus 20, comme le méta-schéma des vues).
- Champ cité seul. Il vise d’abord les aspects du contexte : ceux des types demandés par
type:(obligatoires et suggérés), et ceux déjà cités paraspect.champ. Sans contexte, il vise tous les aspects qui ont ce champ ; pour une énumération, seulement ceux dont la liste contient la valeur. Plusieurs candidats donnent un « ou ». Ainsistatut:actifvise le projet, ettype:tache echeance<7jla tâche. - Valeurs.
- Dates relatives
7j(today + 7d),-3j,2s(semaines),1m, les motsaujourdhui,demain,hier, et les datesAAAA-MM-JJ. - Booléens
oui/non, nombres (virgule admise), énumérations (valeur de la liste) et textes, éventuellement entre guillemets. - Un champ référence ne se cherche pas ainsi, et un tag exclu (
-#x) non plus : les vues n’expriment ni l’un ni l’autre.
- Dates relatives
- Refus. Chaque opérateur refusé produit un message en français, rendu dans
problems(GET /api/search/parse) ou en 400errors.q: on n’exécute jamais une recherche à moitié comprise.
Texte libre : nouvelle clé query.text des vues
- Ce que c’est. Une recherche plein texte facultative sur l’index existant de la recherche simple (ADR 0013). Elle utilise
websearch_to_tsquery, dont la syntaxe est exactement celle demandée : mots,"expression exacte",-exclu. - Pourquoi une clé de vue. Une recherche enregistrée garde ainsi son texte : sans elle, « Enregistrer comme vue » aurait perdu une partie de la recherche.
- Différence avec la recherche simple. Seule la correspondance plein texte s’applique. Les débuts de titre de la recherche simple ne s’appliquent pas.
Exécution : une vue ad hoc
GET /api/search/results?q=&zone=&limit=construit une vue en mémoire et l’exécute par le moteur de vues (ViewService.adHoc), après le contrôle d’une vue deconfig/.- Colonnes : le titre, le type, les champs cités, la date de modification. Tri : les plus récemment modifiés d’abord. Au plus 50 lignes (20 dans la palette).
- Le masquage jusqu’à une date (ADR 0039) s’y applique comme dans toute vue.
Enregistrer comme vue : POST /api/search/views
- Écriture. La requête porte
{ name, label, q }. Le serveur réanalyseqet contrôle la vue, puis écritconfig/views/<name>.yamlet recharge les définitions (DefinitionRegistry.writeView). - Refus. Un nom déjà pris donne 409, et le fichier n’est jamais écrasé. Si le rechargement n’a pas retenu la vue, le fichier est retiré et le serveur répond 400.
- Contenu du fichier. La recherche d’origine y figure en commentaire (fins de ligne et caractères de contrôle remplacés par des espaces, pour qu’elle ne puisse pas ajouter de clé), et les clés sont dans l’ordre des fichiers écrits à la main. On le modifie ensuite comme toute vue.
- Pourquoi écrire dans
config/. C’est la demande. Le domaine reste dansconfig/: une vue enregistrée est une définition comme les autres, versionnable avec le reste. L’application écrit dans ce dossier pour la première fois : c’est le premier pas vers l’édition des définitions depuis l’interface, et seule une vue nouvelle peut y être écrite.
Palette
- Bascule. Dès que la saisie contient un opérateur ou un tag (
hasOperators, même règle que le serveur), la palette passe en recherche à opérateurs. Elle montre alors les compléments, les résultats (le nœud s’ouvre comme d’habitude) et « Enregistrer cette recherche comme vue… ». Les commandes et la capture ne sont pas proposées dans ce mode. - Compléter (
operatorSuggestions, calculé dans le navigateur d’après les schémas des aspects, les types et les tags en usage) :- nom de champ :
echdonneecheance<, avec<pour une date ou un nombre et:sinon ; - valeur : énumération, type, mots de date, oui/non ;
- tag :
#madonne#maison. - Tab ou Entrée applique la proposition sans fermer la palette.
- nom de champ :
- Hors mode opérateurs, les noms de champ sont proposés en dernier groupe : Entrée garde son effet habituel.
- Refus : les opérateurs refusés sont listés en rouge sous la saisie.
- Enregistrement : un petit formulaire demande le nom de la vue (prérempli d’après la recherche). Le nom de fichier en est tiré (
viewSlug). La vue s’ouvre ensuite, et le menu « Vue » relit les vues.
Actions en lot en liste et en kanban (complément de l’ADR 0041)
ViewList et ViewKanban gagnent une case par ligne ou par carte (selectable, selected, selectToggle), que la page des vues n’active que pour elle (pas dans les widgets ni le panneau de contexte). La sélection est partagée entre les affichages d’une vue et vidée au changement d’affichage. La barre ne vise que les nœuds cochés encore affichés.
Analyseur partagé avec la saisie en une ligne ?
L’autre fil prépare une syntaxe de capture en une ligne. Les deux syntaxes se ressemblent (#tag, champ:valeur), mais leur sens diffère : chercher des nœuds n’est pas écrire des valeurs (une date relative, <, l’exclusion). L’analyseur n’est donc pas partagé pour l’instant. Seul le découpage en mots (tokens, guillemets compris) pourrait l’être.
Reporté
- Exclure un tag, et chercher par référence (
projet:Toiture). - Libellés des valeurs d’énumération dans les compléments : les valeurs brutes s’affichent, comme ailleurs.
- Tri et colonnes choisis dans la palette avant d’enregistrer.
- Modifier une vue existante depuis l’interface.
Conséquences
- Toute évolution du langage des vues profite à la recherche, et inversement, puisque la recherche n’est qu’une autre façon de l’écrire.
- L’application peut écrire dans
config/views/. C’est le cas en développement. Dans l’image déployée,config/est monté en lecture seule (docker-compose*.yml) et l’application tourne sous l’utilisateurapp: l’enregistrement y est refusé avec un message clair (400, touteFileSystemException), sans rien écrire. Pour l’activer en production, il faudrait monter./config/views:/config/viewsen écriture et le rendre inscriptible parapp. C’est un choix de déploiement, laissé ouvert.