From 75f962ab46895d8a412dd0f46687667bb854401a Mon Sep 17 00:00:00 2001 From: mathieu Date: Sat, 22 Aug 2026 00:11:37 +0200 Subject: [PATCH] =?UTF-8?q?Fait=20saisir=20un=20par=20un=20ce=20qui=20se?= =?UTF-8?q?=20saisissait=20sur=20une=20ligne=20=C3=A0=20s=C3=A9parateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les thèmes et les auteurs souffraient du défaut corrigé hier sur les unes : un champ relié à chaque frappe sur une propriété qui découpait puis recomposait la liste, donc un séparateur intapable. Ils passent au même geste — écrire, Entrée, puis modifier ou retirer chaque ligne — et le composant est partagé plutôt que recopié une troisième fois. Le bloc des rôles se fond dans la liste des auteurs : il listait les mêmes noms une seconde fois. Renommer une ligne conserve son rôle, parce qu'on corrige un nom au lieu de reconstruire la liste. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 33 +++ .../Composants/ChampsNumeroRevue.razor | 180 +-------------- .../Composants/FormulaireLivre.razor | 110 ++++----- MaBibli.Client/Composants/NomsAuteurs.cs | 80 +++++++ MaBibli.Client/Composants/SaisieListe.razor | 208 ++++++++++++++++++ MaBibli.Client/wwwroot/css/app.css | 39 ++-- MaBibli.Shared/Dtos/EnregistrementLivre.cs | 33 ++- 7 files changed, 419 insertions(+), 264 deletions(-) create mode 100644 MaBibli.Client/Composants/NomsAuteurs.cs create mode 100644 MaBibli.Client/Composants/SaisieListe.razor diff --git a/CLAUDE.md b/CLAUDE.md index f7f2819..b12cda6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3237,6 +3237,39 @@ grisé à champ vide, dégrisé dès la première frappe, regrisé si l'on effac 617 tests au vert (618 avant : le test du découpage des unes n'a plus d'objet). +### Thèmes et auteurs ont suivi — `SaisieListe`, un composant pour les trois (2026-08-22) + +Les deux champs signalés comme « même mécanique, même défaut » ont été traités de la même +façon, et la troisième copie a été évitée : `Composants/SaisieListe.razor` porte le champ, la +liste, `Modifier`, `Supprimer`, le refus du doublon et la notification du parent. Les articles +à la une, les thèmes et les auteurs s'en servent. + +⚠️ **Le fieldset « Rôles » a disparu, fondu dans la liste des auteurs.** Il y avait désormais +deux endroits qui listaient les mêmes noms. Les segments de rôle vivent sur la ligne de leur +auteur — et **la règle « seulement à partir de deux auteurs » est inchangée**, comme le défaut +`NonPrecise`. + +⚠️ **Renommer un auteur conserve son rôle, par construction.** `NomsAuteurs` est une vue +`IList` sur `EnregistrementLivre.Auteurs` dont l'indexeur fait `with { Nom = value }` : +on **corrige une ligne**, on ne la reconstruit pas. C'est ce qui remplace la reprise des rôles +par nom normalisé de `EnregistrementLivre.Decouper` — celle-ci n'existait que parce que le champ +se reconstruisait à chaque frappe. Elle reste en place pour le seul **préremplissage** depuis +une notice, qui donne les auteurs sur une ligne en une affectation. + +⚠️ **La référence de la liste prêtée doit rester stable.** `SaisieListe` se remet à zéro quand +elle change d'identité (c'est ainsi qu'un formulaire vidé ou une autre fiche ne laissent pas +traîner une saisie) : construire la vue `NomsAuteurs` à chaque rendu ferait perdre ce qui est en +cours de frappe à chaque caractère. Elle n'est donc refaite que lorsque `Saisie` change. + +`ThemesTexte` a disparu comme `ArticlesTexte`. `Auteur` reste, **uniquement pour le +préremplissage** — son commentaire le dit, pour qu'on ne la relie pas de nouveau à un ``. + +**Vérifié en exécution** (saisie manuelle puis édition d'une fiche, 320 px) : thèmes et auteurs +rangés un par un, doublon de casse refusé avec son message, rôles posés puis **conservés** en +corrigeant « Christophe » en « Christophé », livre enregistré et relu par l'API avec ses deux +rôles (`Scénario`, `Dessin`) et ses deux thèmes, fiche rouverte en édition avec ses listes +garnies, aucun débordement horizontal. + ## La 7ᵉ série vérifiée en navigateur — trois défauts, tous invisibles aux tests (2026-08-21) La série Q à Y s'était close sur un aveu : « **aucun** écran n'a été regardé s'afficher ». Le diff --git a/MaBibli.Client/Composants/ChampsNumeroRevue.razor b/MaBibli.Client/Composants/ChampsNumeroRevue.razor index 335be27..b1445e0 100644 --- a/MaBibli.Client/Composants/ChampsNumeroRevue.razor +++ b/MaBibli.Client/Composants/ChampsNumeroRevue.razor @@ -32,77 +32,15 @@ @* - ⚠️ Un titre à la fois, et surtout PAS une ligne de titres séparés par des points-virgules. - Le champ unique se reliait à chaque frappe sur une propriété qui découpait puis recomposait - la liste : la saisie était donc réécrite sous les doigts — un point-virgule tapé disparaissait - (il ouvrait une valeur vide, aussitôt élaguée), et l'espace qui le suit était rogné par le - même élagage. Autrement dit, le séparateur du champ était le seul caractère qu'on ne pouvait - pas y taper. - - Ici la liste est la donnée : on écrit un titre, Entrée l'y range, et chaque titre se modifie - ou se retire ensuite. Une virgule (« Ukraine, deux ans après ») n'a plus rien à couper, - puisque plus rien ne découpe. + ⚠️ Un titre à la fois, et surtout PAS une ligne de titres séparés par des points-virgules : + voir SaisieListe, qui porte le pourquoi. Une virgule dans un titre (« Ukraine, deux ans + après ») n'a plus rien à couper, puisque plus rien ne découpe. *@ -
- - -
- - - - - @if (_indexModifie is not null) - { - - } -
- - @if (_messageArticle is not null) - { -

@_messageArticle

- } - - @if (Saisie.Articles.Count > 0) - { -
    - @for (var rang = 0; rang < Saisie.Articles.Count; rang++) - { - @* La copie locale est indispensable : sans elle, les fermetures des deux - boutons captureraient la variable de boucle, donc son état final. *@ - var index = rang; - var titre = Saisie.Articles[rang]; - -
  • - @titre - -
    - - - -
    -
  • - } -
- } -
+ @* ⚠️ Une URL, pas une photo. Aucune source ne donne la couverture d'un NUMÉRO : l'ISSN désigne @@ -165,108 +103,6 @@ /// private void Notifier() => _ = OnChangement.InvokeAsync(); - /// Identifiant du champ « à la une », pour que son libellé le désigne vraiment. - private readonly string _idChamp = $"une-{Guid.NewGuid():N}"; - - /// Titre en cours de frappe. Il n'est relié à rien d'autre : voir plus haut. - private string? _titreSaisi; - - /// Rang du titre qu'on modifie — null = on en ajoute un nouveau. - private int? _indexModifie; - - private string? _messageArticle; - - /// Saisie servie au rendu précédent, pour repérer que le parent en a changé. - private AjoutNumeroRevue? _saisiePrecedente; - - /// - /// Le parent remplace la saisie entière (numéro enregistré, autre numéro ouvert en - /// modification) : ce qui restait dans le champ appartenait au numéro d'avant. - /// - protected override void OnParametersSet() - { - if (!ReferenceEquals(_saisiePrecedente, Saisie)) - { - _saisiePrecedente = Saisie; - RemettreAZero(); - } - } - - private void RemettreAZero() - { - _titreSaisi = null; - _indexModifie = null; - _messageArticle = null; - } - - /// - /// Entrée range le titre, comme le bouton. - /// - /// - /// ⚠️ Cette touche ne remonte pas à OnEntree : celui-ci enregistre le numéro - /// entier, et l'on serait alors bien en peine de saisir deux titres à la une. - /// - private void SurToucheArticle(KeyboardEventArgs e) - { - if (e.Key == "Enter") - { - ValiderArticle(); - } - } - - private void ValiderArticle() - { - var titre = _titreSaisi?.Trim(); - - if (string.IsNullOrEmpty(titre)) - { - return; - } - - // Même règle de doublon que partout ailleurs : au sens de la normalisation, et le - // libellé déjà rangé est celui qui reste. - var cle = NormalisationTexte.Normaliser(titre); - var deja = Saisie.Articles.FindIndex(a => NormalisationTexte.Normaliser(a) == cle); - - if (deja >= 0 && deja != _indexModifie) - { - _messageArticle = "Ce titre est déjà à la une de ce numéro."; - return; - } - - if (_indexModifie is { } index) - { - Saisie.Articles[index] = titre; - } - else - { - Saisie.Articles.Add(titre); - } - - RemettreAZero(); - Notifier(); - } - - /// Reprend un titre dans le champ : on le corrige là où on l'a écrit. - private void ModifierArticle(int index) - { - _messageArticle = null; - _indexModifie = index; - _titreSaisi = Saisie.Articles[index]; - } - - private void AnnulerModification() => RemettreAZero(); - - private void SupprimerArticle(int index) - { - Saisie.Articles.RemoveAt(index); - - // Le titre qu'on modifiait vient peut-être de disparaître, et les rangs suivants ont - // glissé : reprendre la modification écrirait sur le mauvais titre. - RemettreAZero(); - Notifier(); - } - /// /// Date affichée par le champ, en local. /// diff --git a/MaBibli.Client/Composants/FormulaireLivre.razor b/MaBibli.Client/Composants/FormulaireLivre.razor index 16c8cc2..ee17445 100644 --- a/MaBibli.Client/Composants/FormulaireLivre.razor +++ b/MaBibli.Client/Composants/FormulaireLivre.razor @@ -21,9 +21,9 @@ @* ⚠️ L'ORDRE DES CHAMPS EST UNE DÉCISION (lot Q, 2026-08-21), pas une mise en page. - Type de document, puis thèmes, puis auteurs, puis rôles : c'est l'ordre dans lequel on - regarde un livre qu'on tient en main, et surtout le type COMMANDE LA SUITE de la saisie — - les rôles ne se posent que là. Relégué en bas, il obligeait à descendre tout le formulaire + Type de document, puis thèmes, puis auteurs (chacun avec son rôle) : c'est l'ordre dans + lequel on regarde un livre qu'on tient en main, et surtout le type COMMANDE LA SUITE de + la saisie — les rôles ne se posent que là. Relégué en bas, il obligeait à descendre tout le formulaire pour dire « c'est une BD », c'est-à-dire après avoir saisi ce qui en dépend. ⚠️ Remonter le champ le rend visible ; le PRÉREMPLIR écrirait quelque chose de faux. Le @@ -46,61 +46,48 @@ - + @* + ⚠️ Thèmes et auteurs se saisissent UN PAR UN, et non plus sur une ligne à séparateurs. + Le champ unique se reliait à chaque frappe sur une propriété qui découpait puis + recomposait la liste : le point-virgule tapé y était élagué (entrée vide) et l'espace + qui le suit rogné — le séparateur était le seul caractère qu'on ne pouvait pas taper. + Voir CLAUDE.md. + *@ + @* - Un seul champ, plusieurs auteurs : le point-virgule les sépare. La virgule est déjà - prise par la forme bibliographique « Zola, Émile », qu'on doit pouvoir taper telle quelle. - Le serveur rapproche ensuite les variantes sûres d'un auteur déjà connu. - *@ - + Un auteur par ligne, et son RÔLE avec lui. - @* - Les rôles n'apparaissent qu'à partir de DEUX auteurs, et c'est délibéré : un roman n'a - qu'un signataire dont le rôle ne se pose pas, et une ligne de boutons sous chaque saisie - n'apprendrait rien. La question « qui écrit, qui dessine ? » ne se pose qu'à plusieurs. - Le défaut reste « non précisé » : on ne remplit pas une case à la place de l'utilisateur. - *@ - @if (Saisie.Auteurs.Count > 1) - { -
- - Rôles (facultatif — utile pour une BD) - + ⚠️ Le rôle vit sur le LIEN, pas sur la personne : le même auteur scénarise un album et + dessine le suivant. C'est aussi pourquoi renommer une ligne conserve son rôle + (NomsAuteurs) — on corrige un nom, on ne désigne pas quelqu'un d'autre. - @foreach (var (auteur, index) in Saisie.Auteurs.Select((a, i) => (a, i))) + ⚠️ Les rôles n'apparaissent qu'à partir de DEUX auteurs, décision inchangée : un roman + n'a qu'un signataire dont le rôle ne se pose pas. Le défaut reste « non précisé ». + *@ + + + @if (Saisie.Auteurs.Count > 1) { -
- @auteur.Nom -
- @foreach (var role in Roles) - { - - } -
+
+ @foreach (var role in Roles) + { + + }
} -
- } + +
@* Le format n'est plus affiché dans les listes que pour les ebooks : un livre y est donc @@ -212,6 +199,27 @@ private void PoserRole(int index, RoleAuteur role) => Saisie.Auteurs[index] = Saisie.Auteurs[index] with { Role = role }; + /// + /// Les noms des auteurs, vus comme une liste de chaînes par SaisieListe. + /// + /// + /// ⚠️ Sa référence doit rester stable tant que la saisie ne change pas : le composant + /// se remet à zéro quand la liste qu'on lui prête change d'identité, et il perdrait donc à + /// chaque rendu ce qui est en cours de frappe. + /// + private NomsAuteurs _noms = default!; + + private EnregistrementLivre? _saisiePrecedente; + + protected override void OnParametersSet() + { + if (!ReferenceEquals(_saisiePrecedente, Saisie)) + { + _saisiePrecedente = Saisie; + _noms = new NomsAuteurs(Saisie.Auteurs); + } + } + [Parameter, EditorRequired] public EnregistrementLivre Saisie { get; set; } = new(); [Parameter] public string LibelleValidation { get; set; } = "Enregistrer"; diff --git a/MaBibli.Client/Composants/NomsAuteurs.cs b/MaBibli.Client/Composants/NomsAuteurs.cs new file mode 100644 index 0000000..021c107 --- /dev/null +++ b/MaBibli.Client/Composants/NomsAuteurs.cs @@ -0,0 +1,80 @@ +using System.Collections; +using MaBibli.Shared.Dtos; + +namespace MaBibli.Client.Composants; + +/// +/// Vue « liste de noms » des auteurs d'une saisie, pour que SaisieListe les remplisse +/// comme il remplit les thèmes. +/// +/// +/// ⚠️ Un auteur n'est pas un nom : il porte aussi son rôle sur ce livre (scénario, +/// dessin…), qui vit sur le lien et non sur la personne. Renommer par l'indexeur conserve +/// donc le rôle — c'est ce qui remplace la reprise des rôles que faisait +/// EnregistrementLivre.Decouper quand le champ des noms se reconstruisait à chaque +/// frappe : ici la ligne n'est pas reconstruite, elle est corrigée. +/// +/// La liste sous-jacente est celle de la saisie, modifiée en place : cette vue n'en garde +/// aucune copie qui pourrait diverger. +/// +/// +public sealed class NomsAuteurs(IList auteurs) : IList +{ + public int Count => auteurs.Count; + + public bool IsReadOnly => false; + + public string this[int index] + { + get => auteurs[index].Nom; + set => auteurs[index] = auteurs[index] with { Nom = value }; + } + + public void Add(string nom) => auteurs.Add(new AuteurSaisi(nom)); + + public void Insert(int index, string nom) => auteurs.Insert(index, new AuteurSaisi(nom)); + + public void RemoveAt(int index) => auteurs.RemoveAt(index); + + public void Clear() => auteurs.Clear(); + + public bool Contains(string nom) => IndexOf(nom) >= 0; + + public int IndexOf(string nom) + { + for (var i = 0; i < auteurs.Count; i++) + { + if (auteurs[i].Nom == nom) + { + return i; + } + } + + return -1; + } + + public bool Remove(string nom) + { + var index = IndexOf(nom); + + if (index < 0) + { + return false; + } + + auteurs.RemoveAt(index); + return true; + } + + public void CopyTo(string[] tableau, int index) + { + foreach (var auteur in auteurs) + { + tableau[index++] = auteur.Nom; + } + } + + public IEnumerator GetEnumerator() => auteurs.Select(a => a.Nom).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/MaBibli.Client/Composants/SaisieListe.razor b/MaBibli.Client/Composants/SaisieListe.razor new file mode 100644 index 0000000..65d9050 --- /dev/null +++ b/MaBibli.Client/Composants/SaisieListe.razor @@ -0,0 +1,208 @@ +@* + Un champ qui remplit une LISTE : on écrit une valeur, Entrée la range, et chaque ligne se + modifie ou se retire ensuite. + + ⚠️ Il remplace partout le « champ unique à séparateur » (thèmes, articles à la une), qui + était intapable : relié à chaque frappe sur une propriété qui découpait puis recomposait la + ligne, il élaguait le point-virgule tapé (entrée vide) et rognait l'espace qui le suit. Le + seul caractère qu'on ne pouvait pas y taper était son propre séparateur. Voir CLAUDE.md. + + ⚠️ Le composant écrit dans une liste PRÊTÉE par son parent : il doit donc le prévenir + (OnChangement), sinon tout ce que le parent calcule à partir d'elle reste figé. +*@ +
+ + +
+ + + + + @if (_indexModifie is not null) + { + + } +
+ + @if (_message is not null) + { +

@_message

+ } + + @if (Valeurs.Count > 0) + { +
    + @for (var rang = 0; rang < Valeurs.Count; rang++) + { + @* La copie locale est indispensable : sans elle, les fermetures des boutons + captureraient la variable de boucle, donc son état final. *@ + var index = rang; + var valeur = Valeurs[rang]; + +
  • + @valeur + + @* Ce que le parent veut poser sur la ligne — les rôles d'un auteur, par + exemple. Rien par défaut. *@ + @Complement?.Invoke(index) + +
    + + + +
    +
  • + } +
+ } +
+ +@code { + /// La liste remplie, modifiée en place : c'est celle du parent. + [Parameter, EditorRequired] public IList Valeurs { get; set; } = default!; + + [Parameter, EditorRequired] public string Libelle { get; set; } = default!; + + [Parameter] public string? Aide { get; set; } + + [Parameter] public string? Placeholder { get; set; } + + /// Message affiché quand la valeur tapée est déjà dans la liste. + [Parameter] public string MessageDoublon { get; set; } = "C'est déjà dans la liste."; + + /// + /// Ce que le parent ajoute à chaque ligne — les rôles d'un auteur, par exemple. + /// + [Parameter] public RenderFragment? Complement { get; set; } + + /// + /// Prévient le parent que la liste a changé. + /// + /// + /// ⚠️ Sans cela, un bouton du parent qui dépend de la liste reste figé : Blazor ne redessine + /// que le composant qui a traité l'événement. + /// + [Parameter] public EventCallback OnChangement { get; set; } + + private readonly string _idChamp = $"liste-{Guid.NewGuid():N}"; + + /// Valeur en cours de frappe. Elle n'est reliée à rien d'autre. + private string? _saisie; + + /// Rang de la valeur qu'on modifie — null = on en ajoute une nouvelle. + private int? _indexModifie; + + private string? _message; + + /// Liste servie au rendu précédent, pour repérer que le parent en a changé. + private IList? _precedente; + + /// + /// Le parent a changé de liste (autre fiche, formulaire vidé après enregistrement) : ce qui + /// restait dans le champ appartenait à la précédente. + /// + protected override void OnParametersSet() + { + if (!ReferenceEquals(_precedente, Valeurs)) + { + _precedente = Valeurs; + RemettreAZero(); + } + } + + private void RemettreAZero() + { + _saisie = null; + _indexModifie = null; + _message = null; + } + + /// + /// ⚠️ Cette touche ne remonte pas au parent : celui-ci enregistre la fiche entière, et l'on + /// serait bien en peine de saisir deux valeurs. + /// + private void SurTouche(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + Valider(); + } + } + + private void Valider() + { + var valeur = _saisie?.Trim(); + + if (string.IsNullOrEmpty(valeur)) + { + return; + } + + // Doublon au sens de la normalisation, comme partout ailleurs : le libellé déjà rangé + // est celui qui reste. + var cle = NormalisationTexte.Normaliser(valeur); + var deja = Valeurs.ToList().FindIndex(v => NormalisationTexte.Normaliser(v) == cle); + + if (deja >= 0 && deja != _indexModifie) + { + _message = MessageDoublon; + return; + } + + if (_indexModifie is { } index) + { + Valeurs[index] = valeur; + } + else + { + Valeurs.Add(valeur); + } + + RemettreAZero(); + Notifier(); + } + + /// Reprend une valeur dans le champ : on la corrige là où on l'a écrite. + private void Modifier(int index) + { + _message = null; + _indexModifie = index; + _saisie = Valeurs[index]; + } + + private void Annuler() => RemettreAZero(); + + private void Supprimer(int index) + { + Valeurs.RemoveAt(index); + + // La valeur qu'on modifiait vient peut-être de disparaître, et les rangs suivants ont + // glissé : reprendre la modification écrirait sur la mauvaise. + RemettreAZero(); + Notifier(); + } + + private void Notifier() => _ = OnChangement.InvokeAsync(); +} diff --git a/MaBibli.Client/wwwroot/css/app.css b/MaBibli.Client/wwwroot/css/app.css index 0062fb3..4df390e 100644 --- a/MaBibli.Client/wwwroot/css/app.css +++ b/MaBibli.Client/wwwroot/css/app.css @@ -1356,21 +1356,6 @@ body { font-size: 0.85em; } -/* Un nom, sa rangée de rôles : la ligne se replie sur les écrans étroits plutôt que de - comprimer les boutons, qui doivent rester atteignables au pouce. */ -.ligne-role { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.4rem 0.75rem; - margin-top: 0.4rem; -} - -.nom-role { - font-weight: 600; - font-size: 0.9rem; -} - /* ------------------------------------------------------------------ Menu principal (bandeau) @@ -1614,18 +1599,20 @@ body { font-style: italic; } -/* Saisie des unes : un titre par ligne, son texte à gauche et ses deux actions à droite. - ⚠️ min-width: 0 sur le titre est ce qui fait tenir la ligne à 320 px — un titre d'article +/* Saisie d'une liste (thèmes, auteurs, articles à la une) : une valeur par ligne, son texte + à gauche et ses deux actions à droite. + ⚠️ min-width: 0 sur la valeur est ce qui fait tenir la ligne à 320 px — un titre d'article est long, et sans lui il pousserait « Supprimer » hors de l'écran (même leçon que la ligne de création du lot M1, et que l'arbre des séries). */ -.liste-unes { +.liste-saisie { list-style: none; margin: 0.25rem 0 0; padding: 0; } -.ligne-une { +.ligne-saisie { display: flex; + flex-wrap: wrap; gap: 0.5rem; align-items: center; justify-content: space-between; @@ -1633,18 +1620,24 @@ body { border-bottom: 1px solid var(--mb-bord); } -.ligne-une-titre { - flex: 1 1 auto; +.ligne-saisie-valeur { + flex: 1 1 8rem; min-width: 0; overflow-wrap: anywhere; - font-style: italic; } -.ligne-une .actions-tome { +.ligne-saisie .actions-tome { flex: 0 0 auto; margin-top: 0; } +/* Les rôles d'un auteur passent à la ligne sous son nom dès que la place manque : trois + segments et deux boutons ne tiennent pas sur une ligne de téléphone. */ +.segments-role { + flex: 1 1 100%; + margin: 0; +} + /* --- Créer depuis une liste : champ + bouton sur une ligne (lot M1) ---- ⚠️ min-width: 0 sur le champ est ce qui fait tenir la ligne à 320 px : sans lui, un élément de formulaire refuse de rétrécir sous sa largeur intrinsèque diff --git a/MaBibli.Shared/Dtos/EnregistrementLivre.cs b/MaBibli.Shared/Dtos/EnregistrementLivre.cs index 667a042..d9d075d 100644 --- a/MaBibli.Shared/Dtos/EnregistrementLivre.cs +++ b/MaBibli.Shared/Dtos/EnregistrementLivre.cs @@ -67,10 +67,11 @@ public record EnregistrementLivre /// Vue « une seule ligne » de , pour les champs de saisie simples. /// /// - /// Non sérialisée : est la forme de référence sur le fil. Cette - /// propriété existe pour que l'interface puisse lier un <input> unique sans - /// avoir à gérer une liste dynamique — un foyer saisit un auteur dans l'immense majorité - /// des cas, et les rares livres à quatre auteurs se tapent avec des « ; ». + /// ⚠️ Ce n'est plus un champ de saisie : le formulaire remplit la liste un auteur à + /// la fois (voir SaisieListe), un champ relié à chaque frappe sur cette propriété + /// ayant rendu son propre séparateur intapable. Elle ne sert plus qu'au préremplissage + /// depuis une notice, qui donne les auteurs sur une ligne — une seule affectation, jamais + /// une par caractère. /// [JsonIgnore] public string? Auteur @@ -79,13 +80,6 @@ public record EnregistrementLivre set => Auteurs = Decouper(value, Auteurs); } - [JsonIgnore] - public string? ThemesTexte - { - get => Themes.Count == 0 ? null : ListeSaisie.Joindre(Themes); - set => Themes = DecouperThemes(value); - } - /// /// Découpe une saisie texte en auteurs, en écartant les entrées vides. /// @@ -93,10 +87,10 @@ public record EnregistrementLivre /// Auteurs déjà saisis, dont les rôles sont repris quand le nom réapparaît. /// /// - /// ⚠️ Sans cette reprise, corriger une faute de frappe dans le champ des noms effacerait le - /// rôle des auteurs voisins : le champ se relie à chaque frappe, et il reconstruirait la - /// liste entière à « non précisé ». Le rapprochement se fait sur la forme normalisée du nom, - /// pour survivre à un accent ajouté après coup. + /// ⚠️ La reprise des rôles a été écrite pour un champ qui se reconstruisait à chaque frappe. + /// Ce champ n'existe plus — le formulaire corrige un nom sans toucher à son rôle + /// (NomsAuteurs) — mais elle reste juste, et gratuite, pour un préremplissage qui + /// écraserait sinon des rôles déjà posés. /// public static List Decouper(string? saisie, IEnumerable? connus = null) { @@ -120,9 +114,12 @@ public record EnregistrementLivre } /// - /// Découpe la ligne des thèmes. Séparateur : le point-virgule, comme celui des - /// auteurs juste au-dessus dans le même formulaire — la virgule reste acceptée, un thème - /// n'en contenant jamais. + /// Découpe une ligne de thèmes. Séparateur : le point-virgule, la virgule restant + /// acceptée — un thème n'en contient jamais. + /// + /// ⚠️ Plus aucun champ ne s'y lie : les thèmes se saisissent un par un. Cela reste la façon + /// de relire une ligne venue d'ailleurs. + /// /// public static List DecouperThemes(string? saisie) => ListeSaisie.Decouper(saisie);