{#- Macros partagées. À importer : {% from "_composants.html" import alerte, zone_etat %} Aucune de ces macros ne lit le contexte de la page : tout passe par les arguments. Elles posent les attributs `data-*` que lit `statique/ilot.js` (liste fermée de la spec § 1.3) ; les libellés que l'îlot affiche passent par `data-libelle-*`. Contenu : zone_etat, alerte, vie_privee, attributs_brouillon (attributs d'un formulaire modifiable), bouton_enregistrer, barre_enregistrement, actions, bloc_actions, actions_fiche, action_principale, toast, avancement, pastilles_motifs et onglets_page. -#} {#- Zone d'état d'un formulaire ou d'une page : cible des erreurs (`HX-Retarget: #etat-`, plan §2.10). `contenu` : message initial facultatif. -#} {% macro zone_etat(zone, contenu="") -%}
{{ contenu }}
{%- endmacro %} {#- Alerte d'erreur (`role="alert"`). -#} {% macro alerte(message) -%} {%- endmacro %} {#- Texte de vie privée : une fois par zone IA (spec § 1.6), jamais sous chaque bouton. sorte : "offres" (page Offres, sous les onglets), "fiche" (sous l'en-tête de la fiche quand une préparation est proposée), "ia" (bandeau de la lettre), "ia_cv" (bandeau du CV adapté), "ia_cv_libre" (bandeau du CV adapté en mode libre), "ia_cv_base" (bandeau de Mon CV), "creation" (parcours guidé de Mon CV), "documents" (recréer le CV depuis les documents), "gabarit" (écrire l'adaptateur du gabarit Typst personnel), "relecture" (relecture du dossier par un recruteur simulé). "generation" : ancien nom de "offres", gardé jusqu'à CB1. -#} {% macro vie_privee(sorte="ia") -%} {% if sorte in ("offres", "fiche", "generation") -%} Préparer un dossier envoie l'offre, vos règles de rédaction et votre CV de base à Claude (Anthropic). {%- elif sorte == "ia_cv" -%} Le CV (ou l'élément choisi), l'offre, vos règles et votre profil sont envoyés à Claude (Anthropic). Cliquez dans une puce ou dans l'accroche pour ne réécrire qu'elle. {%- elif sorte == "ia_cv_libre" -%} Le CV (ou le passage choisi), l'offre, vos règles et votre profil sont envoyés à Claude (Anthropic). {%- elif sorte == "ia_cv_base" -%} Le CV (ou l'élément choisi), vos règles et votre profil sont envoyés à Claude (Anthropic). Cliquez dans une puce ou dans l'accroche pour ne réécrire qu'elle. {%- elif sorte == "creation" -%} Les documents, votre consigne, vos règles de rédaction et votre profil sont envoyés à Claude (Anthropic). Rien n'est inventé : ce qui manque dans vos documents reste à compléter. {%- elif sorte == "documents" -%} L'IA propose un nouveau CV ; vous le comparez à l'actuel, élément par élément, avant d'accepter. Les documents cochés, vos règles et votre profil sont envoyés à Claude (Anthropic). {%- elif sorte == "relecture" -%} Pour établir la fiche de la société, l'intitulé de l'offre, le nom de la société et l'adresse de l'annonce sont envoyés à Claude (Anthropic), qui peut consulter cette page en ligne. Ensuite, le CV adapté, la lettre, l'offre, la fiche et vos règles de relecture sont envoyés à Claude, sans accès au web. {%- elif sorte == "gabarit" -%} Les fichiers Typst du gabarit et de votre CV d'exemple, ainsi que votre consigne, sont envoyés à Claude (Anthropic). Les images et polices restent sur le poste ; ni profil ni règles de rédaction ne sont envoyés. {%- else -%} La lettre (ou le passage choisi), l'offre, vos règles et votre profil sont envoyés à Claude (Anthropic). {%- endif %} {%- endmacro %} {#- Attributs d'un `
` dont le brouillon est suivi par l'îlot : modifié ou non, état « modifié » (`data-libelle-modifie`), proposition en attente, garde de sortie. À poser dans la balise : ``. `modifie` : le serveur rend un brouillon qui diffère du fichier (`data-modifie="1"`) ; `objet` : de quoi parle la garde de sortie (« La lettre », « Le CV »...) ; `libelle_modifie` : texte d'état d'un formulaire modifié (propre à la page). -#} {% macro attributs_brouillon(modifie=False, objet="La page", libelle_modifie="● Modifications non enregistrées") -%} data-brouillon{% if modifie %} data-modifie="1"{% endif %} data-libelle-modifie="{{ libelle_modifie }}" data-libelle-proposition="Une proposition de l'IA attend votre décision." data-libelle-garde-titre="Modifications non enregistrées" data-libelle-garde-texte="{{ objet }} a changé depuis le dernier enregistrement. Que voulez-vous faire avant de quitter ?" data-libelle-rester="Rester" data-libelle-quitter="Quitter sans enregistrer" data-libelle-enregistrer-quitter="Enregistrer et quitter" {%- endmacro %} {#- Le bouton Enregistrer (envoi du formulaire). `oob` : version échangée hors bande, pour une réponse partielle qui doit le désactiver (proposition en attente). -#} {% macro bouton_enregistrer(zone, libelle="Enregistrer", desactive=False, oob=False) -%} {%- endmacro %} {#- Barre d'enregistrement (spec § 1.2), à poser en dernier dans le ``. Collante en bas de page. `zone` : suffixe des ids (`#etat-` reçoit aussi les erreurs) ; `etat` : texte initial (« ✓ Version 3, enregistrée il y a 2 h ») ; `modifie` : le formulaire diffère du fichier (Annuler visible, Enregistrer actif) ; `boutons` : enregistrer libellé du bouton (« Enregistrer ») annuler {url, cible, select} : GET qui recharge l'éditeur depuis le disque ; `cible` (défaut `#formulaire-`) et `select` (défaut `cible`) telecharger {url, libelle} : lien « Télécharger le PDF » proposition_en_attente vrai : Enregistrer désactivé L'îlot met à jour l'état, Annuler et Enregistrer quand l'utilisateur saisit. -#} {% macro barre_enregistrement(zone, etat, modifie, boutons) -%} {% set b = boutons or {} %} {% set annuler = b.get("annuler") %} {% set telecharger = b.get("telecharger") %}
{{ etat }}
{% if telecharger %} {{ telecharger.get("libelle") or "Télécharger le PDF" }} {% endif %} {% if annuler %} {% set cible = annuler.get("cible") or "#formulaire-" ~ zone %} {% endif %} {{ bouton_enregistrer(zone, b.get("enregistrer") or "Enregistrer", desactive=(not modifie) or b.get("proposition_en_attente")) }}
{%- endmacro %} {#- Une action (bouton, lien, entrée de menu) d'un `contexte.Action`. -#} {% macro _action(a, cible, principale=False, dans_menu=False) -%} {% set classes = "bouton-menu" if dans_menu else ("bouton principal" if principale else "bouton") %} {% if a.methode == "lien" -%} {{ a.libelle }} {%- elif a.desactive or a.occupe -%} {%- else -%} {%- endif %} {%- endmacro %} {#- Actions d'une offre (règle du § 1.5 : un menu « ⋯ » à partir de trois actions, compte de la principale compris ; « Effacer le dossier… » est toujours dans le menu). `principale`, `autres`, `reservee_au_menu`, `etiquette` : ceux d'un `contexte.Actions` ; `id` : id de l'offre ; `titre` : nom lu par les lecteurs d'écran (« Autres actions pour ») ; `cible` : élément que remplace une action `post` (défaut : la carte `#carte-`). -#} {% macro actions(id, principale, autres, reservee_au_menu, titre="cette offre", etiquette="", cible="") -%} {% set zone = cible or "#carte-" ~ id %} {% set total = (autres | length) + (1 if principale else 0) + (1 if reservee_au_menu else 0) %} {% set dans_menu = (autres + [reservee_au_menu]) if reservee_au_menu else autres %} {% set menu = dans_menu if total >= 3 else ([reservee_au_menu] if reservee_au_menu else []) %} {% set directes = [] if total >= 3 else autres %}
{% if etiquette %}{{ etiquette }}{% endif %} {% if principale %}{{ _action(principale, zone, principale=True) }}{% endif %} {% for a in directes %}{{ _action(a, zone) }}{% endfor %} {% if menu %} {% endif %}
{%- endmacro %} {#- Les actions d'une offre et ce qu'il faut dire dessous : pièces manquantes, échec, cause d'indisponibilité de l'IA, et les zones d'erreur `#etat-generation-` et `#etat-carte-` (cibles des erreurs des routes de génération et de statut). `acts` : un `contexte.Actions`. Rend la carte et l'en-tête de la fiche. -#} {% macro bloc_actions(id, titre, acts, cible="") -%} {{ actions(id, acts.principale, acts.autres, acts.reservee_au_menu, titre=titre, etiquette=acts.etiquette, cible=cible) }} {% if acts.manquantes %}

Pièces manquantes : {{ acts.manquantes | join(", ") }}.

{% endif %} {% if acts.message %}

{{ acts.message }}

{% endif %} {% if acts.cause and acts.principale and acts.principale.desactive %}

{{ acts.cause }}

{% endif %} {% if acts.lien_cv %}

Il manque un CV : créez-le dans Mon CV, puis utilisez « Utiliser mon CV de base » dans l'onglet CV adapté.

{% endif %}
{%- endmacro %} {#- Zone d'actions de l'en-tête de la fiche : `#generation-`. Tant qu'une préparation tourne (`en_cours`), elle se sonde toute seule (`every 3s`, `GET /offre//etat`) et se remplace (`outerHTML`). Rendue par `offre_base.html` et, seule, par la route `/etat`. `entete` : `Actions` de `contexte_offre` ; `titre` : nom de l'offre. -#} {% macro actions_fiche(id, entete, en_cours, titre="cette offre") -%}
{{ bloc_actions(id, titre, entete, cible="#generation-" ~ id) }}
{%- endmacro %} {#- L'action de préparation d'une offre (Préparer, Compléter, Préparation en cours…), à poser dans l'état vide d'un onglet (« Pas encore de lettre pour cette offre. »). `etat` : un `contexte.Actions` (celui de l'en-tête) ; ne rend rien si l'offre n'a plus de préparation à proposer. -#} {% macro action_principale(etat, id) -%} {% set p = etat.principale %} {% if p and p.cle in ("preparer", "completer", "en_cours") %}
{{ _action(p, "#generation-" ~ id, principale=True) }} {% if etat.cause and p.desactive %}

{{ etat.cause }}

{% endif %} {% if etat.message %}

{{ etat.message }}

{% endif %} {% if etat.manquantes %}

Pièces manquantes : {{ etat.manquantes | join(", ") }}.

{% endif %}
{% endif %} {%- endmacro %} {#- Message éphémère (spec § 1.4), échangé hors bande dans `#toasts` : à mettre dans la réponse d'un POST. `action` : None, ou {libelle, url, methode, vals, cible, echange} (`methode` "post" : formulaire htmx, par exemple « Annuler » qui reposte l'ancien statut ; "lien" : lien, par exemple « Voir »). Disparaît après 6 s (l'îlot arrête le minuteur tant que le toast a le focus ou le survol). -#} {% macro toast(texte, action=None) -%}
{{ texte }} {% if action and action.get("methode") == "lien" %} {{ action.libelle }} {% elif action %} {% endif %}
{%- endmacro %} {#- Ligne d'avancement d'une carte : `etapes` = [{libelle, etat}], `etat` ∈ "fait" (✓), "en_cours" (…), "a_faire" (○). Le symbole est caché aux lecteurs d'écran, qui lisent « Lettre : fait ». -#} {% macro avancement(etapes) -%}
    {% for e in etapes %} {% set mot = {"fait": "fait", "en_cours": "en cours", "a_faire": "à faire"}[e.etat] %}
  1. {{ e.libelle }}
  2. {% endfor %}
{%- endmacro %} {#- Motifs du score en pastilles : « python +4 » (`motifs` = [{terme, points}]). -#} {% macro pastilles_motifs(motifs) -%} {% if motifs %}
    {% for m in motifs %}
  • {{ m.terme }}{% if m.points %} {{ "+" if m.points > 0 else "" }}{{ m.points }}{% endif %}
  • {% endfor %}
{% endif %} {%- endmacro %} {#- Onglets d'une page (liens, `aria-current="page"`). `onglets` : [{cle, libelle, href, pret, aria_label}] (`pret` vrai ✓, faux ○, None sans symbole) ; `courant` : cle de l'onglet affiché ; `nom` : nom de la barre pour les lecteurs d'écran. -#} {% macro onglets_page(onglets, courant, nom="Sections de l'offre") -%} {%- endmacro %}