Soliguide

🎭 Le showroom des composants

Le site public est réécrit ici, en remplacement de l'application Angular. Le risque de cette réécriture n'est pas de mal refaire un composant — ça se voit — mais d'en oublier un état. Une carte hors ligne, une fermeture temporaire à venir, un accueil sur rendez-vous : chacun existe aujourd'hui, s'affiche différemment, et disparaîtrait sans que personne ne s'en aperçoive avant la production.

Chaque état est donc rendu ici pour de vrai, avec la condition exacte qui le déclenche dans le code d'origine. Les données de lieu sont fictives ; les textes viennent des traductions officielles, parce que ce sont les mots que lisent les personnes qui utilisent le site. 🔍

Et chaque composant est montré deux fois, côte à côte : à gauche le site public Angular, celui qu'on réécrit ; à droite la web-app Svelte, qui affiche déjà la même information — souvent autrement. Elle a tranché des questions que le site public laisse ouvertes, et elle en a rouvert d'autres. Réécrire sans avoir regardé les deux, c'est s'obliger à retrancher plus tard avec moins d'éléments. 🔀

Composants
20
sur deux pages du site
États recensés
227
à ne pas perdre
Aussi dans la web-app
13
sur 20 — le reste n'existe qu'ici
Anomalies
15
⚠️ montrées, pas recopiées

Le rendu est celui de la cible, pas celui des deux applications.

Le site public utilise Bootstrap, la web-app son propre design system Svelte. Les deux sont redessinés ici avec DaisyUI et le design system Soliguide — sinon on comparerait des feuilles de style au lieu de comparer des décisions. Ce qui doit être fidèle, c'est la liste des états, leurs règles métier et leurs textes — pas les pixels.

La page de résultats de recherche

La liste renvoyée par une recherche. C'est la page la plus vue du site, et celle dont un état manquant se remarque le plus tard : il faut la bonne recherche, le bon jour, pour tomber sur une carte hors ligne.

Carte de résultat26 étatsWeb-app : 10

La restitution d'un lieu dans la liste de résultats. Son apparence change entièrement selon le statut de la fiche : le cadre, l'en-tête, la pastille de rang et la couleur du titre en dépendent tous.

Site publicapp-search-place-result16 états

app/modules/search/components/search-place-result/

Ce qui est réécrit. Les conditions sont celles du code Angular.

En ligne, ouverte aujourd'hui
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.status === PlaceStatus.ONLINE && openingTagStatus === OPEN

L'état de référence : c'est celui que voit la très grande majorité des visiteurs.

Brouillon
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

Brouillon

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.status === PlaceStatus.DRAFT

En-tête sans couleur, titre coloré, badge « Brouillon » (clé DRAFT). Ni badge d'horaires ni distance : les deux sont explicitement exclus pour un brouillon.

Hors ligne
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Hors-ligne Horaires non renseignés

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.status === PlaceStatus.OFFLINE

Clé OFFLINE → « Hors-ligne ». La fiche reste consultable, avec ses horaires et sa distance.

Fermée définitivement
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

Fermé définitivement

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Source : Croix-Rouge française Voir les détails

place.status === PlaceStatus.PERMANENTLY_CLOSED

Le cas le plus dépouillé : rang et titre grisés, encart rouge, description sur toute la largeur, et plus rien d'autre — ni services, ni téléphone, ni distance.

Réservée aux professionnels
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui Réservée aux pros

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.visibility === PlaceVisibility.PRO

Clé PLACE_VISIBILITY_PRO. Se cumule avec n'importe quel autre badge, y compris « Brouillon » et « Hors-ligne ».

⚠️ Anomalie : Dans le code actuel ce badge n'a pas la classe d'alerte, seulement le fond — il n'a donc ni la marge intérieure ni la bordure des autres badges, et se retrouve visuellement plus petit sans que ce soit voulu.

Accessible sur orientation
Ouvert aujourd'hui
Cette structure n'est accessible que sur orientation. Si vous êtes professionnel du social, contactez-nous pour avoir plus d'informations sur cette fiche.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.modalities.orientation.checked === true

L'adresse exacte est masquée : seuls le code postal et la ville restent. C'est le seul encart de modalité présent dans la carte — ni « sur rendez-vous » ni « sur inscription » n'y apparaissent.

Fermeture temporaire en cours
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Fermé temporairement
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation. Les personnes accueillies sont orientées vers le centre Belleville.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.closure.actif && status === TEMPORARILY_CLOSED

Le badge d'horaires et la bannière disent la même chose à deux endroits. C'est voulu : le badge se lit en survolant la liste, la bannière donne les dates.

Fermeture temporaire à venir
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui
⚠ Fermeture temporaire : à partir du 01/09/2026
Fermeture annuelle.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.closure.actif && closure.status === INCOMING

Le lieu est encore ouvert aujourd'hui : le badge d'horaires dit « Ouvert » pendant que la bannière annonce la fermeture. Les deux se contredisent en apparence, et c'est correct — c'est justement l'information utile.

Message temporaire en cours
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui
ⓘ Information temporaire : depuis le 12/03/2026
Distribution alimentaire déplacée
La distribution du mardi se tient désormais au gymnase, 8 rue Bichat.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.message.actif && message.status === CURRENT

La carte affiche les messages temporaires en plus des fermetures — ce sont les deux seules familles qu'elle rende. Le lieu reste ouvert : le badge d'horaires ne change pas, seule la bannière s'ajoute.

Message temporaire à venir
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui
ⓘ Information temporaire : à partir du 01/09/2026
Inscriptions en ligne
À la rentrée, l'inscription se fera depuis notre site.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.message.actif && message.status === INCOMING

Même bannière en orange : le message annonce quelque chose qui commence dans quinze jours ou moins.

Fermeture et message empilés
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Fermé temporairement
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation.
ⓘ Information temporaire : depuis le 12/03/2026
Pendant les travaux, l'accueil est assuré au 8 rue Bichat.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.closure.actif && place.tempInfos.message.actif

Rien n'interdit les deux à la fois, et l'ordre est fixe : fermeture d'abord, message ensuite.

Trois bannières empilées
Fermé temporairement
Attention - 14 juillet - Les horaires sont susceptibles de changer, pensez à appeler avant de vous déplacer.
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation.
Cette structure n'est accessible que sur orientation. Si vous êtes professionnel du social, contactez-nous pour avoir plus d'informations sur cette fiche.

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

jour férié + fermeture temporaire + accès sur orientation

Le maximum atteignable dans une carte : jour férié, fermeture temporaire, message, orientation et fermeture définitive sont cinq blocs indépendants, rendus dans cet ordre. La carte peut donc devenir très haute — c'est un cas réel, pas une curiosité.

Horaires temporaires en cours
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.tempInfos.hours.actif

Le lieu a des horaires exceptionnels, et son badge en tient compte : le calcul de l'ouverture du jour lit bien les horaires temporaires. Mais aucune bannière ne le dit dans la carte — il faut ouvrir la fiche pour l'apprendre. À trancher à la migration : c'est une information que la liste de résultats perd aujourd'hui.

Sans service renseigné
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Source : Croix-Rouge française Voir les détails

place.services_all.length === 0

Le titre « Services » reste affiché alors que la liste est vide. À décider au moment de la migration : garder ou masquer le titre.

Sans distance
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

Ouvert aujourd'hui

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

search.location.geoType !== GeoTypes.POSITION || place.distance === 0

La distance n'apparaît que pour une recherche autour d'un point. Une recherche par ville, département ou région ne l'affiche jamais.

Plusieurs numéros de téléphone
1

Accueil de jour Saint-Martin

12 rue des Récollets, 75010 Paris

450 m

Ouvert aujourd'hui

Description

Accueil de jour ouvert à toute personne sans abri. Café, douches, courrier, accompagnement social et repas du midi sur place.

Services

Alimentation Douche Domiciliation
Source : Croix-Rouge française Voir les détails

place.entity.phones.length > 1

Un seul numéro donne un bouton « Afficher le numéro » ; deux ou plus donnent un menu déroulant « Afficher les numéros ». Aucun numéro ne laisse que « Voir les détails ».

Web-appResultsCard + ResultsCardServices10 états

routes/[lang]/(withMenu)/places/components/card/

La même carte, mais l'inverse d'une décision : le site public empile les bannières DANS la carte, la web-app n'en met aucune et pose une icône cliquable qui renvoie à l'ancre correspondante sur la fiche. Elle ajoute deux choses que le site public n'a pas — les horaires du jour et le favori — et en retire une : une fiche indisponible n'est plus consultable du tout.

En ligne, ouverte aujourd'hui
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

place.placeStatus === PlaceStatus.ONLINE

L'état de référence. Deux ajouts par rapport au site public : les horaires du jour en clair sous la pastille (« Aujourd'hui : 9h à 12h - 14h à 17h ») et l'étoile de favori. Le rang de résultat, lui, n'existe pas.

Indisponible — la carte est éteinte
Hors-ligne

Aujourd'hui : 9h à 12h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

placeStatus ∈ {DRAFT, OFFLINE, PERMANENTLY_CLOSED} → isPlaceUnavailable

Un seul état pour les trois statuts. La carte entière devient inerte : le clic est intercepté, le titre n'est plus un lien, la liste de téléphones est vidée (`phones={[]}`), l'itinéraire et « Plus d'infos » sont désactivés. Le site public, lui, garde la fiche consultable dans les trois cas — c'est la divergence la plus lourde des deux applications.

Accès sur orientation
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

75010, Paris

Services

  • Alimentation
  • Douche
  • Domiciliation

place.banners.orientation === true → isDisabled

L'adresse est réduite au code postal et à la ville, comme sur le site public. Mais la web-app va plus loin : la distance disparaît aussi, et le bouton d'itinéraire est désactivé. Le site public garde les deux.

Horaires temporaires en cours — signalés
Ouvert

Aujourd'hui : 10h à 15hⓘ

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

tempInfo.hours === CURRENT && tempInfo.message !== CURRENT

Une icône d'avertissement se pose à côté des horaires du jour et renvoie vers l'ancre #openingHoursSection de la fiche. C'est exactement l'information que la carte du site public perd — le showroom la note comme « à trancher à la migration ». La web-app l'a déjà tranchée, et dans le bon sens.

Message temporaire en cours — une icône, pas de bannière
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martinⓘ

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

tempInfo.message === TempInfoStatus.CURRENT

Le message n'est pas rendu dans la carte : seule une icône apparaît à côté du nom, avec un lien vers l'ancre #tempMessage. La carte reste donc de hauteur constante, là où celle du site public peut empiler cinq bannières et devenir très haute.

Campagne de mise à jour — même icône, priorité plus haute
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martinⓘ

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

place.banners.campaign !== null

La campagne et le message temporaire se disputent le même emplacement, en `else if` : quand les deux sont vrais, seule la campagne s'affiche. Le site public rend les deux bannières, empilées.

Climatisé
Ouvert ❄ Climatisé

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

shouldDisplayThermalComfort(theme.country) && thermalComfort.airConditioned === true

La pastille se range à côté de celle du statut, pas en ruban diagonal. Et la condition n'a QUE le pays : aucune restriction de saison, là où le site public n'affiche le ruban que de juin à septembre.

Plus de trois services
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation
  • + 2

services.slice(0, 3) puis services.slice(3).length > 0

Trois services au maximum, et le reste devient une pastille « + 2 ». Le site public les affiche tous, sans limite — une fiche riche y produit une carte beaucoup plus haute.

En favori
Ouvert

Aujourd'hui : 9h à 12h - 14h à 17h

Accueil de jour Saint-Martin

Source : Croix-Rouge française

12 rue des Récollets, 75010 Paris 450 m

Services

  • Alimentation
  • Douche
  • Domiciliation

$favorites.some((favorite) => favoriteMatches(favorite, place.id, …))

Sans équivalent sur le site public : les favoris n'y existent pas. Ils sont stockés côté navigateur, et la bascule déclenche une notification passagère.

Jour férié — calculé, jamais affiché

Rien n'est affiché

banners.holidays = place.newhours.closedHolidays

⚠️ Anomalie : `searchResult.ts` calcule `banners.holidays` pour chaque résultat, et AUCUN composant ne le lit — vérifié sur tout le paquet. La donnée traverse le serveur, le modèle et le contrat de type pour ne rien produire. Le site public, lui, affiche bien ses trois variantes de jour férié. À la migration, c'est un affichage à reprendre du site public, pas de la web-app.

Badge des horaires du jour14 étatsWeb-app : 8

L'information la plus regardée de la liste. Cinq valeurs possibles, plus un sixième cas où aucun badge n'est affiché — celui qu'on oublie.

Site publicopeningTagStatus6 états

app/modules/search/components/search-place-result/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Ouvert aujourd'hui
Ouvert aujourd'hui

openingTagStatus === PlaceOpeningStatus.OPEN

Clé SEARCH_FILTER_OPEN_TODAY. Un point vert animé précède le texte dans le site actuel ; il est décoratif, le mot porte l'information.

Fermé aujourd'hui
Fermé aujourd'hui

openingTagStatus === PlaceOpeningStatus.CLOSED

Clé SEARCH_FILTER_CLOSED_TODAY. N'arrive que pour une fiche en ligne dont les horaires sont renseignés.

Fermé temporairement
Fermé temporairement

openingTagStatus === PlaceOpeningStatus.TEMPORARILY_CLOSED

Clé TEMPORARILY_CLOSED. Une fermeture temporaire l'emporte sur les horaires habituels.

Horaires non renseignés
Horaires non renseignés

openingTagStatus === UNKNOWN && place.status !== PlaceStatus.DRAFT

Clé TIME_UNKNOWN. C'est aussi l'état de repli quand le calcul ne peut pas conclure.

Ouvert avec services limités
Ouvert avec services limités

openingTagStatus === PlaceOpeningStatus.PARTIALLY_OPEN

Clé PARTIALLY_OPEN. Le lieu est fermé mais au moins un de ses services est ouvert. Même fond que « ouvert », sans le point animé.

Aucun badge

Rien n'est affiché

openingTagStatus === UNKNOWN && place.status === PlaceStatus.DRAFT

Un brouillon dont les horaires sont inconnus n'affiche rien du tout. L'absence est un état : sans elle, on croit avoir couvert les cinq valeurs de l'énumération et on se trompe.

Web-appPlaceStatus8 états

lib/components/PlaceStatus.svelte

Une seule pastille pour deux informations que le site public sépare : l'ouverture du jour ET le statut de la fiche. Une règle de priorité tranche entre les deux, et il n'existe aucun cas où rien ne s'affiche.

Ouvert
Ouvert

openingStatus === PlaceOpeningStatus.OPEN → variant success

Clé OPENED — « Ouvert », pas « Ouvert aujourd'hui ». Le mot « aujourd'hui » est déplacé dans la ligne d'horaires juste en dessous, qui donne les heures exactes.

Ouvert partiellement
Ouvert partiellement

openingStatus === PARTIALLY_OPEN → variant warning

Clé PARTIALLY_OPENED. Différence de couleur : orange ici, vert sur le site public — qui lui donne le même fond qu'« ouvert ».

Fermé
Fermé

openingStatus === CLOSED → variant error

Clé CLOSED.

Fermé temporairement
Fermé temporairement

openingStatus === TEMPORARILY_CLOSED → variant error

Clé TEMPORARILY_CLOSED.

Repli sur le statut de la fiche
Hors-ligne Fermé définitivement Brouillon

openingStatus === UNKNOWN && placeStatus ∈ {DRAFT, OFFLINE, PERMANENTLY_CLOSED}

La règle qui n'a pas d'équivalent : quand l'ouverture est inconnue, la pastille montre le statut de la fiche à la place. Le site public a deux composants distincts pour ça — un badge d'horaires dans la liste, un bandeau de statut sur la fiche.

Horaires non renseignés
Horaires non renseignés

openingStatus === UNKNOWN && placeStatus non indisponible → variant neutral

Clé HOURS_NOT_SET, et le seul état rendu en `type="display"` plutôt qu'en `type="badge"` : une pastille plus discrète, parce qu'elle ne dit rien d'utile.

Aucun cas vide
Horaires non renseignés

le `default` du switch retombe toujours sur HOURS_NOT_SET

La différence de fond avec le site public, qui n'affiche RIEN pour un brouillon aux horaires inconnus. Ici les deux fonctions de correspondance ont un `default`, donc la pastille est toujours rendue. Un état de moins à ne pas oublier — et une information de plus donnée au visiteur.

Brouillon en neutre, pas en orange
Brouillon

placeStatus === PlaceStatus.DRAFT → variant neutral

Le site public met « Brouillon » en orange, comme un avertissement. La web-app le met en gris, comme une information de service. Deux lectures du même statut : à trancher, pas à recopier.

Bannières temporaires dans la carte13 étatsWeb-app : rien

Deux familles seulement dans la carte : la fermeture temporaire et le message temporaire. Chacune se décline en deux moments, quatre formats de date et quatre formes de corps. Les boutons du composant ne s'affichent jamais ici, et les horaires temporaires n'y sont pas rendus du tout — la carte ne les demande pas.

Site publicapp-display-temp-banner13 états

app/modules/search/components/search-place-result/ (lignes 217 à 227)

Ce qui est réécrit. Les conditions sont celles du code Angular.

Fermeture temporaire — en cours
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation.

tempInfos.closure.actif && closure.status === TempInfoStatus.CURRENT

Clés FERMETURE_TEMPORAIRE + DATE_INTERVAL_FROM_TO. Rouge : la fermeture a commencé, le lieu est fermé maintenant.

Fermeture temporaire — à venir
⚠ Fermeture temporaire : à partir du 01/09/2026
Fermeture annuelle.

tempInfos.closure.actif && closure.status === TempInfoStatus.INCOMING

Orange : la fermeture commence dans quinze jours ou moins. Au-delà de quinze jours, rien ne s'affiche du tout — voir l'état « à venir dans plus de quinze jours ».

Message temporaire — en cours
ⓘ Information temporaire : depuis le 12/03/2026
Distribution alimentaire déplacée
La distribution du mardi se tient désormais au gymnase, 8 rue Bichat.

tempInfos.message.actif && message.status === TempInfoStatus.CURRENT

Clé INFO_TEMPORAIRE. C'est la seule autre famille rendue dans la carte. L'icône passe de l'alerte au « i » d'information, mais la couleur reste celle du moment — rouge pour « en cours », y compris pour un message anodin.

Message temporaire — à venir
ⓘ Information temporaire : à partir du 01/09/2026
Inscriptions en ligne
À la rentrée, l'inscription se fera depuis notre site.

tempInfos.message.actif && message.status === TempInfoStatus.INCOMING

Orange. Un message peut donc annoncer quelque chose avant que ça arrive : « à partir du 1ᵉʳ septembre, les inscriptions se font en ligne ».

Fermeture et message ensemble
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation.
ⓘ Information temporaire : depuis le 12/03/2026
Pendant les travaux, l'accueil est assuré au 8 rue Bichat.

tempInfos.closure.actif && tempInfos.message.actif

Les deux blocs sont indépendants : rien n'empêche une fiche d'avoir les deux. La fermeture passe toujours avant le message, dans cet ordre.

Corps — dates seules
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026

tempInfos.name === null && tempInfos.description === null

Le bloc de corps n'est pas rendu du tout : la bannière se réduit à son titre et à ses dates. C'est le cas le plus fréquent d'une fermeture saisie vite.

Corps — nom seul
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation

tempInfos.name !== null && tempInfos.description === null

Le nom s'affiche en gras, sur sa propre ligne.

Corps — description seule
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Pendant les travaux, l'accueil est assuré au 8 rue Bichat. Voir le plan

tempInfos.name === null && tempInfos.description !== null

La description est insérée en HTML brut, tel que la structure l'a saisi : elle peut donc contenir des liens, des listes, du gras. C'est un point de vigilance pour la migration — il faudra continuer à l'accepter, en le nettoyant.

Corps — nom et description
⚠ Fermeture temporaire : du 01/07/2026 au 31/08/2026
Travaux de rénovation
Pendant les travaux, l'accueil est assuré au 8 rue Bichat.

tempInfos.name !== null && tempInfos.description !== null

La forme complète : titre, dates, nom en gras, puis description.

Les quatre formats de date
  • depuis le 12/03/2026pas de date de fin, déjà commencé (DATE_INTERVAL_FROM_PAST)
  • à partir du 01/09/2026pas de date de fin, pas encore commencé (DATE_INTERVAL_FROM_FUTURE)
  • du 01/07/2026 au 31/08/2026période (DATE_INTERVAL_FROM_TO)
  • le 14/07/2026une seule journée (DATE_INTERVAL_THE_DAY)

DateService.translateDateInterval(dateDebut, dateFin)

Quatre formulations selon les dates connues, toutes mises en minuscules à la suite du titre. Une migration qui n'en implémente que deux passera longtemps inaperçue : les deux autres ne sont pas absentes, elles sont fausses.

À venir dans plus de quinze jours — rien

Rien n'est affiché

differenceInCalendarDays(dateDebut, aujourd'hui) > 15 → status FUTURE, actif false

Une fermeture annoncée trois mois à l'avance n'apparaît nulle part. Elle devient visible le quinzième jour avant son début. C'est un choix de produit, pas un oubli — mais il faut le savoir pour ne pas croire à un bug.

Statut « passé » — jamais visible
⚠ Fermeture temporaire : le 14/07/2026

status === TempInfoStatus.OBSOLETE → actif false

⚠️ Anomalie : Le composant sait afficher une information temporaire passée, en bleu. Mais l'affichage exige que l'information soit encore active, et une information passée ne l'est plus. Cette variante bleue est donc inatteignable sur tout le site public — carte et fiche comprises. Elle n'apparaît que dans les écrans d'administration, qui forcent l'affichage.

Horaires temporaires — absents de la carte

Rien n'est affiché

place.tempInfos.hours n'est pas transmis par search-place-result

Vérifié dans le template : la carte n'appelle le composant que pour la fermeture et le message. Un lieu qui a des horaires temporaires n'en montre donc aucune trace dans la liste de résultats — seul son badge d'ouverture en tient compte, puisque le calcul de l'ouverture du jour lit bien les horaires temporaires. La bannière, elle, n'existe que sur la fiche.

Web-appPas d'équivalent

La carte de la web-app ne rend AUCUNE bannière temporaire. À la place, elle pose une icône d'avertissement cliquable — à côté des horaires pour une fermeture ou des horaires temporaires, à côté du nom pour un message — et le contenu ne se lit que sur la fiche, à l'ancre visée. Le choix se défend : la carte garde une hauteur constante, et une liste de résultats reste parcourable. Il a un coût : l'information ne se lit plus sans un clic. Les treize états ci-contre se réduisent donc, côté web-app, aux deux icônes de la « Carte de résultat ».

Bannières d'information par territoire7 étatsWeb-app : rien

Des encarts affichés en tête de liste selon le département cherché et la catégorie demandée. Ils ne dépendent d'aucune fiche : c'est la recherche elle-même qui les déclenche. Quatre encarts existent, tous réservés à Soliguide France.

Site publicapp-results-info-banner7 états

app/modules/search/components/results-info-banner/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Puy-de-Dôme — accompagnement départemental

Information

Vous souhaitez être guidé dans votre recherche ou avoir des précisions sur un dispositif référencé ? Vous pouvez joindre les animatrices du Conseil départemental au 04 73 42 24 45 du lundi au vendredi de 8h30 à 16h ou utiliser le formulaire de contact pour être recontacté. Ce service est disponible en français, anglais et espagnol.

isThemeSoliguideFr && areas.departmentCode === '63'

Clés INFORMATION + PUY_DE_DOME_BANNER_DESCRIPTION. Le seul encart déclenché par un département seul, sans condition de catégorie. Un numéro de téléphone et des horaires en dur dans une traduction : à surveiller, ça se périme.

Hébergement d'urgence — le 115

Information

Pour toute demande d'hébergement d'urgence ou orientation vers les dispositifs adaptés, nous vous conseillons d'appeler le numéro de téléphone gratuit : 115 (disponible partout en France) Il est ouvert 24h/24, 7 jours/7. Un service de traduction simultané lui permet de répondre dans toutes les langues

Beaucoup de personnes appellent le 115, il est donc possible qu'il n'y ait plus de place. Nous vous conseillons de contacter un travailleur social en plus d'appeler le 115, pour trouver d'autres solutions

Plus d'informations ici ou contactez notre équipe à l'aide du service de messagerie.

isThemeSoliguideFr && category === Categories.EMERGENCY_ACCOMMODATION

Clés INFORMATION + PLACE_115. Le seul encart en orange, et de loin le plus long. Il contient un lien vers une fiche pratique, dont l'adresse change avec la langue du visiteur.

⚠️ Anomalie : La traduction contient une balise mal fermée : « <span class="fw-bold">115<span> » au lieu de « </span> ». Comme le texte est inséré en HTML brut, la balise ouverte de trop se propage à tout ce qui suit — la fin de l'encart hérite du gras. À corriger dans la traduction, pas dans le composant.

Conseil administratif — calcul des aides

Information

Calculez en ligne toutes les aides auxquelles vous êtes éligibles sur le site Mes Aides https://www.mesdroitssociaux.gouv.fr/dd1pnds-ria/index.html

isThemeSoliguideFr && category === Categories.ADMINISTRATIVE_ASSISTANCE

Clé CALCUL_AIDE, suivie de l'adresse en clair du simulateur d'aides de l'État. L'adresse est écrite deux fois dans le template : comme lien et comme texte du lien.

Formation et emploi — numéro vers l'emploi

Information

Le numéro gratuit 0801 900 100 répond à toutes les questions sur l'orientation professionnelle (démarches, mises en relation entreprises, accompagnement, etc.) du lundi au vendredi de 9h45 à 17h30, sauf le mardi matin et le vendredi après 16h30. En dehors des horaires, il est possible de laisser un message pour être rappelé.

isThemeSoliguideFr && departmentCode ∈ ['13','31','33','35','44','54','59','69'] && category ∈ enfants de TRAINING_AND_JOBS

Clés TOUSTESPOSSIBLES_BANNER_NUMERO + TOUSTESPOSSIBLES_BANNER_DESCRIPTION. Le seul encart à croiser deux conditions : huit départements et une sous-catégorie de « Formation et emploi » (cours de français, accompagnement à l'emploi, tutorat…). Le plus difficile à voir apparaître, donc le plus facile à perdre.

Deux encarts à la fois

Information

Vous souhaitez être guidé dans votre recherche… (Conseil départemental du Puy-de-Dôme)

Information

Pour toute demande d'hébergement d'urgence… appelez le 115

areas.departmentCode === '63' && category === EMERGENCY_ACCOMMODATION

Les quatre encarts sont indépendants : une recherche d'hébergement d'urgence dans le Puy-de-Dôme en affiche deux, empilés dans l'ordre du template. La liste de résultats démarre alors très bas dans la page.

Aucun encart

Rien n'est affiché

aucune des quatre conditions n'est remplie

L'immense majorité des recherches. Les encarts sont l'exception, pas la règle.

Hors Soliguide France — jamais d'encart

Rien n'est affiché

isThemeSoliguideFr === false

Les quatre encarts sont enfermés dans une seule condition de thème : Soliguide Espagne et Andorre n'en affichent aucun, même sur les catégories concernées. À reprendre tel quel — ces dispositifs sont français.

Web-appPas d'équivalent

Rien de tel dans la web-app : aucune trace de PLACE_115, PUY_DE_DOME_BANNER_DESCRIPTION, CALCUL_AIDE ni TOUSTESPOSSIBLES, et `departmentCode` n'y sert qu'aux paramètres de recherche. Les quatre encarts territoriaux sont une spécificité du site public. Ce sont pourtant les seuls endroits où l'on dit au visiteur d'appeler le 115 : à la migration, il faut les reprendre du site public, la web-app n'en donne aucun modèle.

Bannière des jours fériés5 étatsWeb-app : rien

Affichée seulement les jours fériés du département concerné. Trois variantes selon ce que la structure a déclaré, et rien le reste de l'année.

Site publicapp-display-holidays5 états

app/modules/place/standalone-components/display-holidays/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Fermé les jours fériés
Attention - 14 juillet - Cette structure est fermée en raison d'un jour férié.

place.newhours.closedHolidays === PlaceClosedHolidays.CLOSED

Clés ATTENTION_FERIE + ATTENTION_STRUCTURE_HOLIDAYS_CLOSED.

Ouvert les jours fériés
Cette structure a indiqué être ouverte les jours fériés.

place.newhours.closedHolidays === PlaceClosedHolidays.OPEN

Seule variante sans le préfixe « Attention » en gras — le message est rassurant, pas alarmant. Clé ATTENTION_STRUCTURE_HOLIDAYS_OPEN.

Jours fériés inconnus
Attention - 14 juillet - Les horaires sont susceptibles de changer, pensez à appeler avant de vous déplacer.

place.newhours.closedHolidays === PlaceClosedHolidays.UNKNOWN

Clé ATTENTION_HORAIRES. C'est la valeur par défaut : la plupart des fiches sont dans ce cas.

En tête de liste, pas dans une carte

Attention - 14 juillet

Les horaires sont susceptibles de changer, pensez à appeler avant de vous déplacer.

<app-display-holidays [search]="search"> dans search.component.html

Le même composant a deux rendus selon ce qu'on lui passe. Au-dessus de la liste il reçoit la recherche et affiche un titre suivi d'un paragraphe ; dans une carte il reçoit le lieu et affiche une seule ligne colorée selon ce que la structure a déclaré. Deux mises en forme pour la même information, sur la même page.

Aucun jour férié

Rien n'est affiché

holidays.length === 0

Le composant est toujours présent dans la page et ne rend rien 355 jours par an.

Web-appPas d'équivalent

La web-app calcule bien la donnée — `banners.holidays = place.newhours.closedHolidays` dans `lib/models/searchResult.ts` — mais aucun composant ne la lit. Il n'y a donc ni les trois variantes selon ce que la structure a déclaré, ni le double rendu en tête de liste. Un jour férié ne se voit nulle part dans la web-app.

Ruban de confort thermique8 étatsWeb-app : 4

Un bandeau en diagonale dans le coin de la carte, affiché seulement en France et en Espagne, et seulement de juin à septembre. Il décale le titre et l'adresse pour ne pas les recouvrir.

Site publicapp-thermal-comfort-status4 états

app/modules/shared/components/thermal-comfort-status/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Climatisé
❄ Climatisé

modalities.thermalComfort.airConditioned === true

Clé AIR_CONDITIONED_RIBBON.

Non climatisé
❄ Non climatisé

modalities.thermalComfort.airConditioned === false

Clé NOT_AIR_CONDITIONED_RIBBON. Dire qu'un lieu n'est pas climatisé est une information utile en canicule, pas une absence de donnée.

Chauffé
🔥 Chauffé

heated === true && isWinterSeason() && airConditioned == null

Clé HEATED_RIBBON.

⚠️ Anomalie : Cet état est inatteignable : sa condition exige l'hiver, alors que le composant entier ne s'affiche que de juin à septembre. Il est montré ici pour que la décision de le supprimer ou de le réparer soit prise, plutôt que subie.

Aucun ruban

Rien n'est affiché

airConditioned == null, ou pays hors France et Espagne, ou hors saison

Le cas de très loin le plus fréquent : la donnée de confort thermique est récente et peu remplie.

Web-appTag AIR_CONDITIONED_RIBBON (dans ResultsCard et PlaceHeader)4 états

routes/[lang]/(withMenu)/places/components/card/ResultsCard.svelte

Pas un ruban : une pastille ordinaire, rangée à côté de celle du statut. Deux états au lieu de quatre, et une condition amputée de la saison — ce qui rend l'affichage possible toute l'année.

Climatisé
❄ Climatisé

shouldDisplayThermalComfort(theme.country) && airConditioned === true

Clé AIR_CONDITIONED_RIBBON, la même que sur le site public. Seul le placement change : dans le flux de l'en-tête, donc rien à décaler pour ne pas le recouvrir.

Non climatisé
❄ Non climatisé

shouldDisplayThermalComfort(theme.country) && airConditioned === false

Clé NOT_AIR_CONDITIONED_RIBBON.

Pas d'état « Chauffé »

Rien n'est affiché

aucune branche `heated` dans le composant

Le site public a un état « Chauffé » que sa propre condition rend inatteignable — il exige l'hiver alors que le composant ne s'affiche que de juin à septembre. La web-app ne l'a jamais écrit. C'est un argument de plus pour le supprimer plutôt que le réparer.

Aucune restriction de saison
❄ Climatisé ❄ Non climatisé

shouldDisplayThermalComfort(theme.country) — le pays, et rien d'autre

Le site public enferme le ruban dans une fenêtre juin–septembre. La web-app affiche la pastille toute l'année dès que la donnée existe. Deux réponses opposées à la même question : « non climatisé » est-il une information utile en janvier ? À trancher avec le produit, pas en lisant le code.

Mention de la source11 étatsWeb-app : 6

L'attribution des données reprises d'un partenaire. Elle compte doublement ici : c'est un engagement du projet, et la source cliquable est ce qui permet d'aller corriger l'information à la bonne porte.

Site publicapp-display-sources5 états

app/modules/shared/components/display-sources/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Source sans lien
Source : Alisol (AD2S)

source.toDisplay && !source.externalUrl

Source avec licence

EXTERNAL_SOURCE_MAPPING[name].licenseLink !== undefined

Un seul partenaire est dans ce cas aujourd'hui : DORA via data·inclusion, sous licence ouverte.

Plusieurs sources
Source : DORA via data·inclusion (licence Open Data), Croix-Rouge française

sources.filter((source) => source.toDisplay).length > 1

Séparées par une virgule. Attention : le séparateur suit le dernier élément du tableau complet, pas le dernier élément affiché — une source masquée en fin de liste laisse une virgule orpheline.

Aucune source à afficher

Rien n'est affiché

sources.every((source) => !source.toDisplay)

Le cas normal d'une fiche saisie directement dans Soliguide.

Web-appDisplaySource6 états

lib/components/DisplaySource.svelte

Le même travail en quarante lignes, et un bug de séparateur en moins. Deux différences de fond : la source est du texte, jamais une pastille, et elle apparaît DEUX fois — dans l'en-tête de la carte de résultat, et en pied de la fiche.

Source avec lien

source.url est renseignée

Clé SOURCE. Le lien s'ouvre dans un nouvel onglet (`target="_blank"`), ce que le site public ne fait pas.

Source sans lien
Source : Alisol (AD2S)

source.url est vide

Source avec licence

source.licenseLabel && source.licenseLink

Les deux champs sont exigés ensemble : une licence sans lien ne s'affiche pas. Le site public n'a pas cette garde.

Plusieurs sources — la virgule est juste
Source : DORA via data·inclusion, Croix-Rouge française

index < sources.length - 1

La différence qui compte : le séparateur suit l'index DANS LA LISTE RENDUE. Le site public le fait suivre le dernier élément du tableau complet, donc une source masquée en fin de liste y laisse une virgule orpheline. La web-app n'a pas ce défaut, parce qu'elle ne filtre pas — c'est le serveur qui ne lui envoie que les sources à afficher.

Aucune source

Rien n'est affiché

place.sources.length === 0 — le bloc entier n'est pas rendu

Le mot « Source » lui-même disparaît, alors que le site public garde une pastille vide dans certains cas. Le filtrage `toDisplay` n'existe pas côté web-app : il est fait en amont.

Deux emplacements, deux couleurs
Source : Croix-Rouge française Source : Croix-Rouge française

color="inverse" dans la carte, color="neutral" sur la fiche

Le même composant sert sur fond coloré et sur fond blanc, via une propriété. Le site public a un seul rendu, et affiche la source uniquement dans la carte.

Barre de filtres — pastilles14 étatsWeb-app : 6

Quatre filtres à bascule et un bouton qui ouvre les filtres avancés. Chaque pastille a deux états, et l'état actif doit se voir autrement que par la couleur seule — d'où la coche.

Site publicapp-search / filter-pill8 états

app/modules/search/components/search/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Ouvert aujourd'hui — inactif

filters.openToday === false

Clé SEARCH_FILTER_OPEN_TODAY.

Ouvert aujourd'hui — actif

filters.openToday === true

L'attribut aria-pressed passe à true, et une coche apparaît.

Accessible en fauteuil — inactif

filters.pmr === false

Clé ACCESS_CONDITION_PEOPLE_WITH_REDUCED_MOBILITY.

Animaux acceptés — actif

filters.animal === true

Clé ACCESS_CONDITION_ACCEPTED_ANIMALS.

Lieu climatisé — inactif

showThermalComfortFilter && filters.airConditioned === false

Cette pastille a sa propre couleur, différente des trois autres. Elle n'existe qu'en France et seulement de juin à septembre.

Lieu climatisé — actif

filters.airConditioned === true

Filtres masqués

showFilters === true

⚠ Le drapeau est inversé dans le code : showFilters à true veut dire que les filtres sont masqués. Une migration qui lit le nom du champ sans lire le template inverse le comportement.

Filtres affichés

showFilters === false

Web-appResultsFilters6 états

routes/[lang]/(withMenu)/places/components/ResultsFilters.svelte

Les quatre mêmes filtres à bascule, dans un ruban qui défile horizontalement. Trois différences : ils sont TOUJOURS les quatre, l'état actif est porté par un vrai `input`, et il n'y a pas de bouton pour replier la barre.

Ouvert aujourd'hui — inactif

!selectedFilters.includes('openToday')

Clé SEARCH_FILTER_OPEN_TODAY, la même. L'ordre diffère : openToday, airConditioned, pmr, animal — le climatisé est en deuxième position, pas en dernière.

Ouvert aujourd'hui — actif

selectedFilters.includes('openToday')

L'état coché vit sur l'`input` du `ToggleButton`, pas sur un attribut `aria-pressed` posé à la main. Un état de moins à tenir synchronisé.

Lieu climatisé — sans condition de pays ni de saison

toujours rendu — SEARCH_RESULT_FILTERS contient les quatre

La divergence la plus nette. Le site public masque cette pastille hors de France et hors de la période juin–septembre. La web-app la montre partout, tout le temps, et sans couleur particulière : les quatre filtres se ressemblent.

Accessible en fauteuil, animaux acceptés

selectedFilters.includes('pmr') / includes('animal')

Clés ACCESS_CONDITION_PMR et ACCESS_CONDITION_ACCEPTED_ANIMALS. Attention : le site public emploie ACCESS_CONDITION_PEOPLE_WITH_REDUCED_MOBILITY pour le même filtre — deux clés pour un même libellé, à unifier à la migration.

Pas de bouton pour masquer les filtres

Rien n'est affiché

aucun `showFilters` dans le composant

Le site public a un drapeau `showFilters` dont le nom est inversé — à true, les filtres sont masqués. La web-app n'a pas ce bouton du tout : les quatre pastilles sont toujours visibles, dans un ruban qui défile. Un piège de migration en moins.

Traduction des filtres en paramètres d'API

Rien n'est affiché

buildSearchResultApiFilters(selectedFilters)

Pas un état visuel, mais la règle métier qui va avec : `pmr` et `animal` deviennent `modalities.pmr` / `modalities.animal`, `airConditioned` devient `modalities.thermalComfort.airConditioned`, et `openToday` reste à la racine. C'est le contrat à retrouver dans l'API v2.

Barre de filtres — menus déroulants7 étatsWeb-app : rien

Cinq menus à choix unique : langues, situation administrative, situation familiale, publics spécifiques, genre. Le libellé du bouton change quand une valeur est choisie.

Site publicapp-filter-pill-dropdown7 états

app/modules/search/components/filter-pill-dropdown/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Fermé, sans valeur

value === null

Le libellé seul, et le premier choix du menu est « Tous ».

Fermé, avec valeur

value !== null && selectedOption !== undefined

Le libellé devient « Étiquette : valeur ». C'est ce qui permet de voir un filtre actif sans ouvrir le menu.

Ouvert

état du menu déroulant

Des boutons radio, donc un seul choix à la fois. Le menu défile au-delà d'une certaine hauteur : la liste des langues en compte près de cent quatre-vingts. Le menu est montré déplié sous son bouton : dans le produit c'est un `dropdown`, dont le contenu se superpose à la page — il recouvrirait la vignette d'à côté.

Valeur absente des options

value !== null && options.find((option) => option.value === value) === undefined

Cas limite atteignable en bricolant l'adresse de la page : la pastille s'affiche active, mais sans pouvoir nommer la valeur choisie.

Champ âge, vide

filters.age === undefined

Le seul filtre qui n'est pas une pastille. Clé AGE.

Champ âge, rempli

filters.age est un nombre entre 0 et 99

Champ âge, valeur refusée

valeur hors de l'intervalle 0–99

La valeur est effacée et un message passager apparaît : « La valeur entrée dans le filtre âge ne semble pas correcte » (clé AGE_BAD_VALUE).

Web-appPas d'équivalent

La web-app n'a aucun filtre à menu déroulant. `SEARCH_RESULT_FILTERS` déclare exactement quatre bascules, et rien d'autre : ni langues, ni situation administrative, ni situation familiale, ni publics spécifiques, ni genre, ni champ d'âge. Cinq menus et un champ numérique disparaissent — dont le filtre par langue, qui compte près de cent quatre-vingts valeurs. À la migration, c'est une question de produit avant d'être une question de code : ces filtres servent-ils, ou la web-app a-t-elle eu raison de s'en passer ?

La vue d'une fiche

Le détail d'un lieu. Plus de composants, des bandeaux qui se cumulent, et le tableau des horaires — qui à lui seul concentre le plus grand nombre d'états de tout le site.

Bannière de statut de la fiche15 étatsWeb-app : 5

Le bandeau en haut de la fiche qui prévient que quelque chose ne va pas : brouillon, structure hors ligne, fermeture définitive, accès sur orientation. Une fiche en ligne et normale n'en affiche aucun.

Site publicapp-display-place-infos10 états

app/modules/place/components/display-place-infos/

Ce qui est réécrit. Les conditions sont celles du code Angular.

En ligne

Rien n'est affiché

place.status === PlaceStatus.ONLINE

L'immense majorité des fiches. Aucun bandeau : c'est l'état de référence contre lequel se lisent tous les autres.

Brouillon
Brouillon
Cette fiche est incomplète, c'est un brouillon.

place.status === PlaceStatus.DRAFT

Clés DRAFT + PLACE_IS_INCOMPLETE.

Brouillon, avec droit d'édition
Brouillon
Cette fiche est incomplète, c'est un brouillon. Continuez à l'éditer

place.status === PlaceStatus.DRAFT && canEdit

Un lien s'ajoute pour reprendre la saisie. Clé CONTINUE_EDITING.

Fermée définitivement
✕ Fermé définitivement

place.status === PlaceStatus.PERMANENTLY_CLOSED

Clé PERMANENTLY_CLOSED. Le bandeau le plus court de la fiche, et le plus définitif.

Hors ligne — visiteur non connecté
⚠ Structure hors-ligne
Cette structure n'a pas été mise à jour depuis 6 mois, essayez de la contacter avant de vous y rendre.

place.status === PlaceStatus.OFFLINE && !me

Clé STRUCTURE_HORS_LIGNE_MESSAGE_NOT_LOGGED. C'est le message que voit le grand public : le plus court, et le seul qui dise « avant de vous y rendre ».

Hors ligne — professionnel sans droit sur la fiche
⚠ Structure hors-ligne
Cette structure n'a pas été mise à jour depuis 6 mois, essayez de la joindre avant d'y orienter quelqu'un, et n'hésitez pas à nous contacter si vous y voyez une information incorrecte.

place.status === OFFLINE && me && !me.admin && !haveRightOnThisPlace

Clé STRUCTURE_HORS_LIGNE_MESSAGE_LOGGED. Le message invite à signaler une erreur, pas à la corriger.

Hors ligne — peut modifier la fiche
⚠ Structure hors-ligne
Cette structure n'a pas été mise à jour depuis 6 mois, apportez-y les modifications nécessaires si besoin ou confirmez simplement que les informations sont bien correctes.

place.status === OFFLINE && (me.admin || (me.role !== 'READER' && haveRightOnThisPlace))

Clé STRUCTURE_HORS_LIGNE_MESSAGE_CAN_EDIT. Le seul message qui demande une action à la personne qui le lit.

Hors ligne — lecteur de l'organisation
⚠ Structure hors-ligne
Cette structure n'a pas été mise à jour depuis 6 mois, contactez une personne de votre organisation ou envoyez-nous un message via le chat afin que des informations y soient modifiées si nécessaire ou que l'ensemble des informations y soit reconfirmé.

place.status === OFFLINE && me.role === 'READER' && haveRightOnThisPlace

Clé STRUCTURE_HORS_LIGNE_MESSAGE_READER. Le plus long des quatre : la personne a le droit de voir mais pas de modifier, il faut donc lui expliquer à qui s'adresser.

Hors ligne — deux messages à la fois
⚠ Structure hors-ligne
Cette structure n'a pas été mise à jour depuis 6 mois, apportez-y les modifications nécessaires si besoin ou confirmez simplement que les informations sont bien correctes.Cette structure n'a pas été mise à jour depuis 6 mois, essayez de la joindre avant d'y orienter quelqu'un, et n'hésitez pas à nous contacter si vous y voyez une information incorrecte.

place.status === OFFLINE && me.admin && !haveRightOnThisPlace

⚠️ Anomalie : Les conditions des quatre messages ne s'excluent pas complètement : une personne administratrice qui n'a pas de droit sur cette fiche déclenche deux messages, empilés l'un sous l'autre. Le rendu ci-contre est bien ce que fait le site aujourd'hui.

Accès sur orientation
Cette structure n'est accessible que sur orientation.
Si vous êtes professionnel du social, contactez-nous pour avoir plus d'informations sur cette fiche.

place.modalities.orientation.checked === true

Bloc indépendant du statut : il se cumule avec n'importe lequel des précédents. Clés ORIENTATION_SEARCH + ORIENTATION_SEARCH_MESSAGE.

Web-appPlaceHeader5 états

routes/[lang]/places/[identifier]/components/PlaceHeader.svelte

Le contraste le plus net des deux applications : là où le site public a dix bandeaux, la web-app n'en a aucun. Elle n'a pas de connexion, donc pas de rôles, donc pas de messages adressés aux professionnels — et sur la fiche elle ne montre même pas le statut de la fiche.

Le statut de la fiche n'est pas transmis
Ouvert

<PlaceStatus openingStatus={status} /> — sans placeStatus

⚠️ Anomalie : `PlaceStatus` sait afficher « Brouillon », « Hors-ligne » et « Fermé définitivement » : c'est ce qu'il fait dans la carte de résultat, qui lui passe les deux propriétés. La fiche ne lui passe que l'ouverture du jour. Une fiche hors ligne ouverte depuis un lien direct n'affiche donc AUCUN avertissement — alors que le composant capable de le faire est déjà là, à un attribut près. C'est un oubli, pas un choix : les deux appels sont incohérents entre eux.

Aucun message de structure hors ligne

Rien n'est affiché

aucune notion d'utilisateur connecté dans le paquet

Les quatre messages du site public — visiteur, professionnel sans droit, personne pouvant modifier, lecteur de l'organisation — n'ont pas d'équivalent : la web-app est un site public sans authentification. Vérifié : ni `me`, ni `canEdit`, ni `haveRightOnThisPlace`, ni la clé STRUCTURE_HORS_LIGNE_MESSAGE. Elle échappe donc aussi à l'anomalie du site public, où une personne administratrice sans droit sur la fiche déclenche deux messages empilés.

Aucun bandeau d'accès sur orientation

Rien n'est affiché

onOrientation ne sert qu'à `disabled` dans GoToButton

Le site public affiche un bandeau orange : « Cette structure n'est accessible que sur orientation ». La web-app ne dit rien — elle se contente de masquer l'adresse exacte et de désactiver le bouton d'itinéraire. L'information la plus utile, savoir qu'on ne peut pas s'y présenter seul, n'est donnée nulle part.

Une icône plutôt qu'un bandeau

Accueil de jour Saint-Martin ⓘ

campaignBanner !== null, sinon tempInfo.message

La seule chose que l'en-tête signale : une icône d'avertissement à côté du nom, qui renvoie à l'ancre #bannerMessage plus bas dans la page. Même mécanique que dans la carte de résultat, et même ordre de priorité — la campagne l'emporte sur le message.

Les horaires du jour, en clair
  • Aujourd'hui9h à 12h - 14h à 17h
  • Fermédu 01/07/2026 au 31/08/2026

!isObjectEmpty(todayInfo)

Ce que le site public ne fait nulle part : donner l'ouverture du jour en heures. « Aujourd'hui : 9h à 12h - 14h à 17h », ou une plage de fermeture quand la structure est fermée pour plusieurs jours. C'est l'information la plus demandée, et elle est ici en haut de page.

Bannières de mise à jour10 étatsWeb-app : 5

Quatre familles distinctes, souvent confondues parce qu'elles se ressemblent et parlent toutes de fraîcheur de l'information. Elles n'ont ni les mêmes conditions ni le même destinataire.

Site publicapp-place-update-campaign-banner5 états

app/modules/place/components/place-update-campaign-banner/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Campagne saisonnière — à mettre à jour

✨ Mise à jour saisonnière : le compte à rebours est lancé ⏳ !
Vous œuvrez dans cette structure ? Prenez deux minutes pour renseigner les modifications saisonnières et assurez des portes ouvertes aux usagers 🚪.

Je renseigne mes changements saisonniers

needUpdate && canEdit && campaignIsActive && campaigns.runningCampaign.toUpdate

La seule bannière avec des boutons, et la seule adressée à la structure elle-même. Clés TO_UPDATE_CAMPAIGN_BANNER, NO_CHANGE_THIS_SEASON, FILL_SEASONAL_CHANGES.

Fiche issue d'une source externe

La mise à jour saisonnière n'a pas encore été effectuée. Il est conseillé d'appeler avant de vous déplacer.

isFromExternalSource(place) && campaignIsActiveForPlace

Clé EXTERNAL_SOURCE_CAMPAIGN_BANNER. Personne ne peut la mettre à jour depuis Soliguide : la bannière ne propose donc aucune action, elle prévient seulement.

Mise à jour en cours
⚠ Cette structure est en cours de mise à jour pour la période en cours. Les horaires sont susceptibles de varier. Il est conseillé d'appeler avant de vous déplacer.

displayCampaignInfo(place)

Clé CAMPAIGN_BANNER_MESSAGE. Adressée au visiteur, pendant que la structure remplit sa mise à jour.

Dernière mise à jour

Dernière mise à jour : 12 août 2026

toujours affiché

Clé EXPORTS_HEADER_LAST_UPDATE. Ce n'est pas une bannière mais une ligne de texte sous la carte — et c'est précisément ce qu'une migration oublie en premier, parce que ça ne ressemble à rien.

Aucune bannière

Rien n'est affiché

hors période de campagne

L'état de la plus grande partie de l'année. Seule la ligne « Dernière mise à jour » subsiste.

Web-appCampaignBanner5 états

routes/[lang]/places/[identifier]/components/CampaignBanner.svelte

Quinze lignes contre quatre familles de bandeaux. La web-app ne connaît que deux messages, tous deux adressés au visiteur, et aucun bouton : elle ne demande jamais rien à la structure.

Mise à jour saisonnière non faite

Les horaires de cette structure n'ont pas été mis à jour récemment. Il est conseillé d'appeler avant de vous déplacer.

displayCampaignInfo(place, false) → WEBAPP_CAMPAIGN_BANNER_MESSAGE

Clé WEBAPP_CAMPAIGN_BANNER_MESSAGE. Conditions cumulées : la campagne est active, sa date d'affichage est passée, `campaign.toUpdate` est vrai et son statut n'est pas FINISHED. Le site public exige en plus `canEdit` pour sa version à boutons — d'où deux bandeaux là où la web-app n'en a qu'un.

Fiche issue d'une source externe

La mise à jour saisonnière n'a pas encore été effectuée. Il est conseillé d'appeler avant de vous déplacer.

isFromExternalSource(place) → EXTERNAL_SOURCE_CAMPAIGN_BANNER

Même clé que le site public. La condition, elle, est différente : la web-app remonte le département depuis le code postal de la fiche (`getDepartmentCodeFromPostalCode`) pour savoir si la campagne est active là-bas. Le site public ne fait pas ce détour.

Jamais de bouton

Rien n'est affiché

<InfoBlock withIcon variant="warning" text={…} /> — et rien d'autre

Le bandeau à deux boutons du site public — « Aucun changement cette saison » et « Je renseigne mes changements saisonniers » — n'existe pas. C'est cohérent : sans connexion, la web-app ne peut rien faire enregistrer à une structure. À la migration, ces boutons sont à reprendre du site public si on veut garder ce chemin de mise à jour.

La ligne de dernière mise à jour

Dernière mise à jour : 12 août 2026

toujours affichée, dans PlaceInfoSection

La web-app la garde, avec une icône et un mois en toutes lettres (`formatToDateWithFullMonth`), en pied du bloc d'informations plutôt que sous la carte. C'est la ligne que le site public affiche aussi, et celle qu'une migration oublie en premier parce qu'elle ne ressemble à rien.

Aucune bannière

Rien n'est affiché

campaignBanner === null

Hors période de campagne, comme sur le site public. Seule la ligne de dernière mise à jour subsiste.

Bannière temporaire, avec ses boutons16 étatsWeb-app : 9

Le même composant que dans la liste de résultats, mais la fiche lui transmet de quoi afficher ses boutons de bascule entre horaires habituels et horaires temporaires. Trois variantes de bouton, dont l'absence de bouton.

Site publicapp-display-temp-banner (fiche)7 états

app/modules/place/standalone-components/display-temp-banner/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Fermeture temporaire, avec retour aux horaires habituels

⚠ Fermeture temporaire : depuis le 01/07/2026

tempInfoType === CLOSURE && displayHoursWhenTempClosed && !admin

C'est l'état exact des captures d'écran de la demande. Clés FERMETURE_TEMPORAIRE, DATE_INTERVAL_FROM_PAST, BTN_HORAIRES_HABITUELS.

Horaires temporaires, non affichés

⚠ Horaires temporaires : du 01/07/2026 au 31/08/2026

tempInfoType === HOURS && !displayTempHours

Clés TEMPORARY_HOURS + BTN_HORAIRES_TEMPORAIRES.

Horaires temporaires, affichés

⚠ Horaires temporaires : du 01/07/2026 au 31/08/2026

tempInfoType === HOURS && displayTempHours

Le bouton bascule dans l'autre sens. Le tableau des horaires en dessous change de contenu en même temps.

Message temporaire, sans bouton

ⓘ Information temporaire : depuis le 12/03/2026

Distribution alimentaire déplacée

La distribution du mardi se tient désormais au gymnase, 8 rue Bichat.

tempInfoType === MESSAGE

En haut de fiche, le message est transmis en mode administration : aucun bouton, jamais.

Fermeture d'un service, en cours

⚠ Ce service est fermé temporairement du 01/07/2026 au 31/08/2026

tempInfoType === CLOSURE && tempInfos.isService && status === CURRENT

Clé SERVICE_FERME_TEMP. Trois libellés différents selon le moment, tous distincts de la fermeture du lieu entier.

Fermeture d'un service, à venir

⚠ Ce service fermera temporairement à partir du 01/09/2026

tempInfoType === CLOSURE && tempInfos.isService && status === INCOMING

Clé SERVICE_FERMERA_TEMP. Seul INCOMING est atteignable : au-delà de quinze jours avant le début, l'information n'est pas encore active et rien ne s'affiche.

Fermeture d'un service, passée — jamais visible

⚠ Ce service a eu une fermeture temporaire du 01/02/2026 au 28/02/2026

tempInfoType === CLOSURE && tempInfos.isService && status === OBSOLETE

⚠️ Anomalie : La clé OLD_SERVICE_TEMP_CLOSE (« Ce service a eu une fermeture temporaire ») et la variante bleue de la bannière ne sont atteignables nulle part sur le site public : l'affichage exige que l'information soit encore active, et une information passée ne l'est plus. C'est du code et une traduction qui ne servent qu'aux écrans d'administration, lesquels forcent l'affichage. À ne pas reporter dans la migration sans décider si on veut vraiment montrer les fermetures passées.

Web-appTempInfoBanner9 états

routes/[lang]/places/[identifier]/components/TempInfoBanner.svelte

Le composant le plus proche de son homologue, et pourtant quatre règles y diffèrent : deux couleurs au lieu de trois, deux formats de date au lieu de quatre, trois libellés de bouton au lieu de deux, et la description d'horaires temporaires volontairement effacée.

Fermeture temporaire en cours

Fermeture temporaire

Du 01/07/2026 au 31/08/2026

tempInfoType === CLOSURE && status === CURRENT → variant error

Clé EXPORTS_HEADER_TEMP_CLOSURE. C'est le SEUL cas rouge de tout le composant : la couleur se déduit de `status === CURRENT && type === CLOSURE`, tout le reste est orange. Le site public met en rouge tout ce qui est « en cours », y compris un message anodin.

Fermeture temporaire à venir — sans bouton

Fermeture temporaire à venir

À partir du 01/09/2026

type === CLOSURE && status === INCOMING → withButton = false

Clé TEMPORARY_CLOSURE_TO_COME. La seule combinaison qui n'a pas de bouton, et c'est logique : les horaires habituels sont déjà ceux affichés, il n'y a rien à basculer. Le site public, lui, n'a pas de bouton du tout sur cette variante non plus — les deux se rejoignent ici.

Horaires exceptionnels en cours

Horaires exceptionnels

Du 01/07/2026 au 31/08/2026

type === HOURS && status === CURRENT → variant warning

Clé EXCEPTIONAL_OPENING_HOURS. Orange, jamais rouge, même en cours.

Horaires exceptionnels à venir

Horaires exceptionnels à venir

À partir du 01/09/2026

type === HOURS && status === INCOMING

Clé EXCEPTIONAL_OPENING_HOURS_TO_COME, que le site public n'a pas : il emploie le même titre pour les deux moments. Une nuance de plus, gratuite.

La description des horaires temporaires est effacée

Horaires exceptionnels

Du 01/07/2026 au 31/08/2026

getDescription() renvoie '' dès que tempInfoType === HOURS

⚠️ Anomalie : La règle est écrite en clair : `if (!description || tempInfoType === TempInfoType.HOURS) return ''`. Ce que la structure a saisi pour expliquer ses horaires exceptionnels — « ouverture réduite pendant les travaux » — n'est jamais montré. Le site public l'affiche. La donnée existe, elle est transmise au composant, et il la jette.

Deux formats de date, pas quatre
  • Du 01/07/2026 au 31/08/2026les deux dates connues
  • À partir du 01/09/2026dateFin absente — passé comme futur

getFormatedDates() : dateDebut + dateFin, ou dateDebut seule

Clés APPLICABLE_TEMP_INFO_DATES et APPLICABLE_TEMP_INFO_START_DATE_ONLY. Le site public en a quatre, dont « le 14/07/2026 » pour une journée unique et une distinction entre « depuis le » et « à partir du ». La web-app dit « à partir du » dans les deux cas — donc une fermeture déjà commencée est annoncée au futur.

La branche MESSAGE est morte

Rien n'est affiché

getTitle(TempInfoType.MESSAGE) → info.name

⚠️ Anomalie : Le composant traite le cas MESSAGE, mais ses deux seuls appels — dans OpeningHoursSection — lui passent CLOSURE et HOURS. Le message temporaire de la fiche est rendu ailleurs, directement dans `+page.svelte`, par un `InfoBlock` posé à la main. La branche MESSAGE et le libellé de bouton qui va avec ne servent donc jamais.

Message temporaire — rendu à part, sans bouton

Distribution alimentaire déplacée

La distribution du mardi se tient désormais au gymnase, 8 rue Bichat. Voir plus

$pageStore.placeDetails.tempInfo.message, dans +page.svelte

Toujours orange, titre = le nom de l'information, texte = sa description, avec un dépliant « Voir plus » au-delà de trois lignes. Pas de dates affichées, et pas de bouton — comme sur le site public. Un rendu écrit à la main plutôt que confié au composant prévu : les deux ne resteront pas alignés longtemps.

Fermeture d'un service

Ce service est fermé temporairement du 01/07/2026 au 31/08/2026

buildServiceTempClosure() + PlaceServices

Quatre libellés selon le moment et la présence d'une date de fin : SERVICE_TEMPORARY_CLOSED, _INCOMING, _START_DATE_ONLY, _START_DATE_ONLY_INCOMING. Le site public en a trois, dont un inatteignable. Ici le filtre est posé à la construction (`status` doit être CURRENT ou INCOMING), donc le cas passé n'existe même pas dans les données.

Jours fériés sur la fiche1 étatWeb-app : rien

Les trois variantes sont les mêmes que dans la liste de résultats — voir la section précédente. Ce qui change ici, c'est l'emplacement.

Site publicapp-display-holidays (fiche)1 état

app/modules/place/standalone-components/display-holidays/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Rendu deux fois sur la même page
Attention - 14 juillet - Les horaires sont susceptibles de changer, pensez à appeler avant de vous déplacer.

… le reste de la fiche …

Attention - 14 juillet - Les horaires sont susceptibles de changer, pensez à appeler avant de vous déplacer.

holidays.length > 0

⚠️ Anomalie : Le composant est appelé à deux endroits de la fiche : dans le bandeau de statut en haut de page, et de nouveau dans la colonne des horaires. Un jour férié affiche donc deux fois le même avertissement. À trancher à la migration : lequel des deux emplacements garder.

Web-appPas d'équivalent

Aucun affichage de jour férié dans la web-app, ni sur la fiche ni dans la liste. La web-app échappe donc au double rendu signalé ci-contre — mais par omission, pas par correction : elle ne montre l'information nulle part. Le site public reste la seule référence pour ces trois variantes.

Publics accueillis12 étatsWeb-app : 6

Une phrase composée à la volée : un en-tête, puis le genre, l'âge, la situation administrative, la situation familiale et les publics spécifiques, dans cet ordre, séparés par des virgules. Trois en-têtes possibles, et six formulations différentes pour l'âge seul.

Site publicapp-display-publics-inline6 états

app/modules/place/components/display-publics-inline/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Accueil inconditionnel

Accueil inconditionnel

publics.accueil === WelcomedPublics.UNCONDITIONAL

Clé PUBLICS_WELCOME_UNCONDITIONAL. La composition s'arrête là : aucun détail n'est ajouté, même si les autres champs sont remplis.

Accueil exclusif

Accueil exclusif : en situation régulière.

publics.accueil === WelcomedPublics.EXCLUSIVE

Clé PUBLICS_WELCOME_EXCLUSIVE. Le détail suit, en minuscules et terminé par un point.

Accueil inconditionnel adapté

Accueil inconditionnel adapté aux femmes, personnes isolées, victimes de violence.

publics.accueil === WelcomedPublics.PREFERENTIAL

Clé PUBLICS_WELCOME_PREFERENTIAL. La formulation la plus délicate : le lieu accueille tout le monde, mais est pensé pour un public précis.

Âge — les six formulations
  • personnes mineures (moins de 18 ans)de 0 à 18
  • personnes majeuresde 18 à 99
  • personnes âgées de 25 ans minimumborne haute par défaut
  • personnes âgées de 25 ans maximumborne basse par défaut
  • personnes âgées de 30 ansles deux bornes égales
  • personnes âgées de 18 à 25 ansintervalle

publics.age.min !== 0 || publics.age.max !== 99

Six clés distinctes selon les bornes. Une migration qui n'en implémente que deux ou trois produit des phrases fausses, pas des phrases absentes — c'est plus difficile à repérer.

Phrase complète, toutes catégories

Accueil exclusif : Femmes, personnes âgées de 18 à 25 ans, demandeuses d'asile, femmes enceintes, victimes de violence.

plusieurs catégories renseignées

L'ordre est fixe et la première lettre est mise en majuscule après coup. C'est ce qui rend la phrase lisible malgré la composition automatique.

Autre information importante

Accueil exclusif : en situation régulière.

Autre information importante : Personnes âgées de plus de 65 ans (ou de plus de 60 ans si déclarées « inaptes au travail ») ou personnes en situation de handicap, titulaires de la carte d'accès aux restaurants.

publics.description est renseignée

Clé INFO_IMPORTANTE. Texte libre, affiché en italique — c'est là que les structures mettent les conditions que le modèle de données ne sait pas exprimer.

Web-appPlaceInfoSection + buildPublics6 états

routes/[lang]/places/[identifier]/components/PlaceInfoSection.svelte

La même information, mais découpée autrement : le site public compose une phrase, la web-app en fait deux entrées d'une liste unique qui mélange publics, modalités et langues. Et la phrase elle-même n'est plus fabriquée par le front — elle vient de `@soliguide/common`.

Accueil inconditionnel
  • Accueil inconditionnel

publics.accueil === UNCONDITIONAL → WELCOME_UNCONDITIONAL

Le titre vient de `getTitleAndIcon()`, la valeur de `translatePublics(i18nInstance, lang, publics, true, false)` — la MÊME fonction de `common` que le site public appelle. Les six formulations d'âge et l'ordre des catégories sont donc identiques des deux côtés, et n'ont pas à être réécrits.

Accueil exclusif
  • Accueil exclusifen situation régulière

publics.accueil === EXCLUSIVE → WELCOME_EXCLUSIVE

Le titre est sur sa propre ligne, en gras, et la valeur en dessous — pas « Accueil exclusif : en situation régulière » d'un seul trait. C'est une décision de lisibilité pour un écran de téléphone.

Accueil inconditionnel adapté
  • Accueil inconditionnel adaptéfemmes, personnes isolées, victimes de violence

publics.accueil === PREFERENTIAL → WELCOME_UNCONDITIONAL_CUSTOM

Renommé : PREFERENTIAL devient « inconditionnel personnalisé » et non « adapté aux ». Deux libellés pour la même valeur d'énumération — à unifier à la migration, c'est la formulation la plus délicate des trois.

Autre information importante
  • Autre information importantePersonnes âgées de plus de 65 ans ou en situation de handicap. Voir plus

publics.description est renseignée → PUBLICS_MORE_INFO

Une entrée séparée dans la liste, pas une ligne accolée à la précédente. Le texte passe par DOMPurify avant affichage, et se replie au-delà de trois lignes — le site public l'affiche entier, en italique, sans nettoyage.

Des pastilles d'accroche
★ Accueil prioritaire

tags.length > 0 → <Tag variant="warning"> avec une étoile

Le gabarit prévoit des pastilles orange à étoile sous chaque entrée. `buildPublics` et `buildModalities` renvoient tous deux `tags: []` : personne n'en produit aujourd'hui. Un emplacement prêt et vide, à ne pas confondre avec un état.

Le nettoyage du HTML saisi

Rien n'est affiché

DOMPurify.sanitize() sur translatedText et sur chaque description

Une règle métier que le site public n'a pas : tout texte saisi par une structure est nettoyé avant d'être inséré. Le site public l'insère brut — le showroom signale d'ailleurs une traduction à balise mal fermée qui contamine tout ce qui suit. À reprendre côté web-app, sans hésiter.

Modalités d'accueil18 étatsWeb-app : 7

Neuf lignes indépendantes, toutes cumulables, dans un ordre fixe. Six d'entre elles ont deux formes : avec ou sans texte de précision. C'est ici que vit le « sur rendez-vous » — ce n'est pas un composant à part.

Site publicapp-display-modalities-inline11 états

app/modules/place/components/display-modalities-inline/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Accès libre

Accès libre

modalities.inconditionnel === true

Clé FREE_ACCESS. La seule ligne sans gras et sans icône. Valeur par défaut du modèle : la plupart des fiches l'affichent.

Sur orientation
  • Sur orientation uniquement : orientation par un travailleur social, une association ou le 115

Fiches d'orientation à télécharger

  • Fiche de liaison.pdf

modalities.orientation.checked === true

Clé ACCESS_CONDITION_ON_ORIENTATION → « Sur orientation uniquement ». Peut être suivie de fiches d'orientation à télécharger.

Sur inscription
  • Sur inscription : Carte d'accès à retirer auprès de votre Espace Parisien des Solidarités

modalities.inscription.checked === true

Clé ACCESS_CONDITION_ON_REGISTRATION.

Sur rendez-vous
  • Sur rendez-vous

modalities.appointment.checked === true

Clé ACCESS_CONDITION_ON_APPOINTMENT. C'est le « Sur RDV » demandé : la quatrième des neuf lignes de ce bloc, pas un composant distinct.

Sur rendez-vous, avec précision
  • Sur rendez-vous : prise de rendez-vous par téléphone du lundi au jeudi

modalities.appointment.checked === true && precisions !== null

Six des neuf lignes ont cette seconde forme. La précision est du texte libre, ajouté après deux-points.

Participation financière
  • Participation financière requise : Prix définis selon les revenus

modalities.price.checked === true

Clé ACCESS_CONDITION_FINANCIAL_PARTICIPATION_REQUIRED.

Accessible en fauteuil
  • Accessible aux personnes à mobilité réduite

modalities.pmr.checked === true

Clé ACCESS_CONDITION_PEOPLE_WITH_REDUCED_MOBILITY. Jamais de précision : c'est oui ou rien, ce qui est une limite du modèle de données plus qu'un choix d'affichage.

Animaux acceptés
  • Animaux acceptés

modalities.animal.checked === true

Clé ACCESS_CONDITION_ACCEPTED_ANIMALS. Information décisive pour les personnes qui vivent à la rue avec un chien.

Autres précisions
  • Autres précisions

Critère d'attribution de l'aide : habiter Paris depuis au moins 3 ans

modalities.other est renseigné

Clé ACCESS_CONDITION_OTHER_PRECISIONS. Contient du HTML saisi par la structure, affiché tel quel.

Toutes les lignes cumulées

Accès libre

  • Sur inscription : Carte d'accès à retirer auprès de votre Espace Parisien des Solidarités
  • Participation financière requise : Prix définis selon les revenus
  • Accessible aux personnes à mobilité réduite
  • Animaux acceptés

plusieurs modalités renseignées

L'ordre d'affichage est celui du template, jamais celui de la saisie.

Accès libre et sur rendez-vous en même temps

Accès libre

  • Sur rendez-vous

modalities.inconditionnel === true && modalities.appointment.checked === true

⚠️ Anomalie : Les deux se contredisent : un lieu en accès libre n'est pas sur rendez-vous. L'exclusion existe ailleurs dans le code mais pas dans ce template, donc la fiche affiche les deux si la donnée est incohérente. À traiter à la migration, en affichage ou à la saisie.

Web-appbuildModalities + PlaceInfoSection7 états

lib/models/placeDetails.ts

Dix entrées possibles au lieu de neuf lignes, dans la même liste que les publics. Deux règles y sont franchement meilleures : « accès libre » exclut vraiment les autres modalités, et le confort thermique devient une ligne d'information.

Accès libre — exclusif des autres
  • Accès libre

modalities.inconditionnel === true → seul ACCESS_FREE est produit

LA différence à retenir. Le ternaire est explicite : si `inconditionnel` est vrai, orientation, rendez-vous et inscription ne sont même pas construits. L'incohérence signalée ci-contre — « accès libre » et « sur rendez-vous » affichés ensemble — est impossible ici. C'est la web-app qui a raison, et c'est la règle à reprendre.

Sur orientation, rendez-vous, inscription
  • Sur orientationorientation par un travailleur social, une association ou le 115
  • Sur rendez-vousprise de rendez-vous par téléphone du lundi au jeudi
  • Sur inscription

!inconditionnel && modalities.<type>.checked === true

Les trois sont construits par la même fonction, avec leurs précisions facultatives. L'ordre est fixe : orientation, rendez-vous, inscription — le site public place l'inscription avant le rendez-vous. Un détail, mais un détail à trancher.

Participation financière, fauteuil, animaux
  • Participation financière requisePrix définis selon les revenus
  • Accessible aux personnes à mobilité réduite
  • Animaux acceptés

modalities.price.checked / pmr.checked / animal.checked

`price` accepte une précision, `pmr` et `animal` non — exactement comme le site public. La limite est dans le modèle de données, pas dans l'affichage, et les deux applications la subissent pareil.

Climatisé — une ligne d'information
  • Lieu climatisé
  • Lieu non climatisé

modalities.thermalComfort.airConditioned === true → AIR_CONDITIONED

Le confort thermique apparaît DEUX fois dans la web-app : en pastille près du statut, et en ligne de modalité ici. Le site public n'en fait qu'un ruban. Et cette ligne-ci n'a aucune condition de pays ni de saison : elle s'affiche dès que la donnée existe.

Autres précisions
  • Autres précisionsCritère d'attribution de l'aide : habiter Paris depuis au moins 3 ans

modalities.other est renseigné → MODALITIES_MORE_INFO

Rangé en deuxième position, juste après le bloc de modalités et avant la participation financière. Le site public le met en dernier. Comme le reste, le contenu passe par DOMPurify.

Pas de fiches d'orientation à télécharger

Rien n'est affiché

aucun document dans buildModalities

Le site public propose des fiches de liaison en téléchargement sous la modalité « sur orientation ». La web-app n'expose aucun document. Pour un professionnel qui doit remplir une fiche de liaison, c'est une fonction manquante — à reprendre du site public.

Toutes les lignes cumulées
  • Sur inscriptionCarte d'accès à retirer auprès de votre Espace Parisien des Solidarités
  • Participation financière requisePrix définis selon les revenus
  • Accessible aux personnes à mobilité réduite
  • Animaux acceptés

plusieurs modalités renseignées

L'ordre du tableau retourné : modalités d'accès, autres précisions, participation financière, fauteuil, animaux, confort thermique. Puis les langues, à la suite dans la même liste.

Langues parlées4 étatsWeb-app : 2

La liste des langues parlées sur place. Près de cent quatre-vingts valeurs possibles, langue des signes comprise.

Site publicapp-display-languages2 états

app/modules/place/components/display-languages/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Plusieurs langues

Langue(s) parlée(s) : Français, Arabe, Anglais, Langue des Signes

place.languages.length > 0

Clé LANGUES → « Langue(s) parlée(s) ». Chaque langue a sa propre clé, préfixée LANGUE_.

Liste vide

Langue(s) parlée(s) :

place.languages est un tableau vide

Le titre reste affiché, suivi de rien. À décider à la migration : masquer le bloc entier.

Web-appbuildSpokenLanguages2 états

lib/models/placeDetails.ts

Deux états côté site public, un seul ici — et c'est le bon : quand il n'y a pas de langue, le bloc n'est pas construit du tout.

Plusieurs langues
  • Langue(s) parlée(s)Français, Arabe, Anglais, Langue des Signes

language?.length → LANGUAGES_SPOKEN

Mêmes clés que le site public (`LANGUE_` + la valeur en majuscules), même séparateur. La différence est de forme : le titre est sur sa propre ligne, puis la liste des langues.

Liste vide — le bloc n'existe pas

Rien n'est affiché

language?.length est faux → tableau vide retourné

Le site public garde le titre « Langue(s) parlée(s) : » suivi de rien, et le showroom le note comme « à décider à la migration ». C'est décidé : la web-app ne construit pas l'entrée. La règle à reprendre.

Affichage des horaires16 étatsWeb-app : 8

Le tableau de la semaine. Sept lignes, mais six contenus possibles par ligne, deux ordres de jours et deux mises en valeur. C'est le composant qui a le plus d'états de toute la fiche.

Site publicapp-display-horaires8 états

app/modules/place/standalone-components/display-horaires/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Ouvert 24h/24

Ouvert 24/24h

hours.h24 === true

Clé OPEN_24. Remplace tout le tableau : il n'y a pas de lignes de jours du tout.

Semaine ordinaire
Lundi9:00 à 12:00-14:00 à 17:00
Mardi9:00 à 12:00
MercrediFermé
Jeudi9:00 à 12:00-14:00 à 17:00
Vendredi9:00 à 12:00
SamediFermé
DimancheFermé

hours.h24 === false && hours.isOpeningHoursSet

Le jour courant est mis en valeur par une bordure et un fond. Ici, lundi.

Deux créneaux dans la journée
Lundi9:00 à 12:00-14:00 à 17:00

hours[day].timeslot.length > 1

⚠️ Anomalie : Les créneaux sont collés par un tiret sans espace : « 9:00 à 12:00-14:00 à 17:00 ». C'est l'effet d'une règle de style qui insère le séparateur, et c'est illisible. À corriger à la migration — mais volontairement, pas par accident.

Fermé temporairement
MercrediFermé temporairement
JeudiFermé temporairement
VendrediFermé temporairement
SamediFermé temporairement
DimancheFermé temporairement
LundiFermé temporairement
MardiFermé temporairement

isTempClosed === true

Toutes les lignes disent la même chose, et les jours sont réordonnés à partir d'aujourd'hui — ici mercredi. C'est l'état de la capture d'écran de la demande.

Ouverture partielle
Lundi9:00 à 12:00
MardiOuvert avec services limités
MercrediFermé

isPartiallyOpen === true && !hours[day].open

Clés PARTIALLY_OPEN + PARTIALLY_OPEN_INFO dans une infobulle. Le lieu est fermé mais certains services sont accessibles.

Horaires non renseignés
LundiHoraires non renseignés
MardiHoraires non renseignés
MercrediHoraires non renseignés
JeudiHoraires non renseignés
VendrediHoraires non renseignés
SamediHoraires non renseignés
DimancheHoraires non renseignés

!hours.isOpeningHoursSet

Clé TIME_UNKNOWN. Le tableau existe quand même, avec sept lignes vides de sens — c'est ce qui explique l'état « Horaires non renseignés » du badge de la liste de résultats.

Itinéraire — heures de passage
Lundi9:00
MardiPas de passage
Mercredi14:00
JeudiPas de passage
VendrediPas de passage
Samedi10:00
DimanchePas de passage

isPlace === false

Pour un parcours mobile, on n'affiche que l'heure de début : c'est un passage, pas une plage d'ouverture. Et le jour sans passage dit « Pas de passage » (clé PAS_PASSAGE), pas « Fermé ».

Jours fermés masqués
Lundi9:00 à 12:00
Jeudi9:00 à 12:00

displayClosedDays === false

Utilisé pour les horaires d'un service et les points d'un itinéraire : seuls les jours ouverts apparaissent. Le tableau peut donc n'avoir qu'une seule ligne.

Web-appOpeningHoursSection + ServiceHours8 états

routes/[lang]/places/[identifier]/components/OpeningHoursSection.svelte

Une liste, pas un tableau : jour à gauche, horaires à droite. Quatre contenus possibles par ligne au lieu de six, l'ordre des jours jamais réordonné, et pas d'état « ouvert 24h/24 ».

Semaine ordinaire
  • Lundi 9h à 12h - 14h à 17h
  • Mardi 9h à 12h
  • Mercredi Fermé
  • Jeudi 9h à 12h - 14h à 17h
  • Vendredi 9h à 12h
  • Samedi Fermé
  • Dimanche Fermé

status !== UNKNOWN && closureDisplayMode !== TEMPORARY

Le jour courant garde la bordure colorée à gauche et un fond — la même mise en valeur que le site public. Les créneaux sont joints par « - » AVEC des espaces (`.join(' - ')`), là où le site public les colle sans espace. Le défaut d'illisibilité signalé ci-contre est donc déjà corrigé ici.

Fermé temporairement
  • Lundi Fermé temporairement
  • Mardi Fermé temporairement
  • Mercredi Fermé temporairement
  • Jeudi Fermé temporairement
  • Vendredi Fermé temporairement
  • Samedi Fermé temporairement
  • Dimanche Fermé temporairement

closureDisplayMode === DisplayMode.TEMPORARY

Toutes les lignes disent la même chose, comme sur le site public. Mais l'ordre des jours ne change PAS : la semaine reste lundi→dimanche. Le site public réordonne à partir d'aujourd'hui, ce qui donne deux lectures différentes du même écran.

Horaires non renseignés
  • Lundi Horaires non renseignés
  • Mardi Horaires non renseignés
  • Mercredi Horaires non renseignés
  • Jeudi Horaires non renseignés
  • Vendredi Horaires non renseignés
  • Samedi Horaires non renseignés
  • Dimanche Horaires non renseignés

status === PlaceOpeningStatus.UNKNOWN

Clé TIME_UNKNOWN, sur les sept lignes — comme le site public. Noter la source de la condition : c'est le STATUT D'OUVERTURE qui la déclenche, pas `hours.isOpeningHoursSet`. Deux chemins pour la même conclusion, à vérifier au moment de reprendre le calcul.

Itinéraire — « Pas de passage »
  • Lundi 9h
  • Mardi Pas de passage
  • Mercredi 14h
  • Jeudi Pas de passage

placeType !== PlaceType.PLACE && openingHours.length === 0

Clé NO_PASSAGE, comme sur le site public. La distinction se lit sur `placeType`, pas sur un booléen `isPlace`.

Pas d'état « ouvert 24h/24 »

Rien n'est affiché

aucune occurrence de h24 ni de OPEN_24 dans le paquet

Le site public remplace tout le tableau par « Ouvert 24/24h ». La web-app n'a pas ce cas : un lieu ouvert en continu y affiche ses sept lignes d'horaires. Vérifié sur tout le paquet — c'est un état à reprendre du site public.

Pas d'infobulle d'ouverture partielle

Rien n'est affiché

PARTIALLY_OPEN n'apparaît que dans PlaceStatus

L'ouverture partielle se lit sur la pastille de statut en haut de page, jamais dans le tableau. Le site public met « Ouvert avec services limités » sur la ligne du jour, avec une infobulle explicative. Une information localisée là où elle sert, contre une information globale.

Horaires d'un service — jours fermés masqués
  • Mardi 14h à 17h
  • Jeudi 14h à 17h

service.differentHours → buildHours(service.hours, false)

Le second paramètre `allDays` à false ne garde que les jours ouverts, exactement comme le `displayClosedDays` du site public. Le tableau d'un service peut donc n'avoir qu'une ligne.

Les bannières temporaires sont dans la section

Fermeture temporaire

Du 01/07/2026 au 31/08/2026

tempInfo.closure puis tempInfo.hours, avant la liste

Les deux bannières vivent DANS la section des horaires, au-dessus de la liste, et l'ordre est fixe : fermeture puis horaires. Le site public les place en haut de la fiche, loin du tableau qu'elles expliquent.

Bouton partager3 étatsWeb-app : rien

Deux boutons côte à côte, et une fenêtre modale au clic. Ce n'est pas un menu déroulant : le contenu s'ouvre par-dessus la page, avec un texte modifiable avant envoi.

Site publicapp-share-place3 états

app/modules/place/components/share-place/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Fermé

état initial

Clés PARTAGER + IMPRIMER. Le bouton « Imprimer » est masqué sur petit écran — il n'y a donc qu'un seul bouton sur téléphone.

Modale ouverte

clic sur « Partager »

Clés EDIT_SHARE_TEXT, COPIER_PRESSE_PAPIER. Le texte est pré-rempli avec le nom, l'adresse et le lien de la fiche, et reste modifiable avant partage. Clique sur « Partager » pour l'ouvrir : la modale DaisyUI est pilotée par une case à cocher, donc elle s'ouvre et se ferme sans JavaScript — dans une vignette, elle ne peut pas être montrée ouverte, une modale occupe l'écran entier.

Texte pré-rempli sans l'adresse exacte

modalities.orientation.checked && me === null

Sur une fiche accessible seulement sur orientation, le texte partagé ne contient que le code postal et la ville. Deux variantes du même champ, faciles à perdre.

Web-appPas d'équivalent

Ni partage ni impression dans la web-app. Le pied de fiche n'a que deux boutons — itinéraire et téléphone (`PlaceFooter.svelte`) — et rien dans le paquet ne touche au presse-papier de partage ni à `navigator.share`. La modale à texte modifiable, les cinq canaux et la variante d'adresse masquée sont une fonction propre au site public, à reprendre telle quelle.

Affichage d'un service16 étatsWeb-app : 8

Un service rendu par la structure. Le titre se compose de plusieurs morceaux, et quatre blocs facultatifs peuvent s'ajouter dans le corps. Le cas le plus fréquent est aussi le plus dépouillé.

Site publicapp-display-services8 états

app/modules/place/components/display-services/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Titre seul

Distribution alimentaire

aucun champ facultatif renseigné

L'état le plus fréquent en base, et le plus facile à oublier en refaisant le composant à partir d'un exemple riche.

Avec catégorie parente

Alimentation : Distribution alimentaire

getParentCategoryIfNeedPrefix(category) !== null

Certaines catégories ne se comprennent pas seules : le nom de la catégorie parente est alors ajouté devant, séparé par deux-points.

Risque de saturation

Distribution alimentaire ⚠ Risque de saturation

service.saturated.status === ServiceSaturation.MODERATE

Clé SATURATION_MODERATE. Trois niveaux existent, mais le niveau bas n'affiche aucun badge — ce qui fait trois états visuels pour trois valeurs.

Saturé, avec précision

Distribution alimentaire ⚠ Saturé : plus de places jusqu'à la fin du mois

service.saturated.status === HIGH && precision !== ''

Clé SATURATION_HIGH. La précision est du texte libre ajouté après deux-points, dans le badge lui-même.

Service complet

Alimentation : Distribution alimentaire

⚠ Ce service est fermé temporairement du 01/07/2026 au 31/08/2026

Colis alimentaires à retirer sur place, sur présentation d'un justificatif de domicile.

Accueil exclusif : personnes isolées.

  • Sur inscription

Horaires

Mardi14:00 à 17:00
Jeudi14:00 à 17:00

differentPublics && differentModalities && differentHours

Les blocs « publics » et « modalités » n'apparaissent que si le service diffère de la structure. Sinon la fiche ne les répète pas, ce qui est un choix d'affichage à conserver.

Colis alimentaires — tous les champs de sa catégorie

Alimentation : Colis alimentaires

Colis à retirer sur place, sur présentation d'un justificatif de domicile.

Produits proposés : Produits frais, Produits secs, Conserves, Fruits et légumes, Produits d'hygiène, Autre (couches et lait infantile)

Adapté aux régimes : Végétarien, Halal, Sans gluten

Choix libre dans les produits

S'adapte du mieux possible aux contraintes alimentaires de chacun.e

service.category === Categories.FOOD_PACKAGES

Un cas réel : cette catégorie déclare sept champs spécifiques. Cinq s'affichent, deux non — l'origine nationale et l'origine biologique ne sont visibles que dans les écrans de gestion.

Tous les champs spécifiques affichables

Alimentation : Distribution alimentaire : déjeuner

↑ le champ de titre est collé au nom de la catégorie, après deux-points, en minuscules

Liste des métiers : maraîchage, second de cuisine, agent d'entretien

Produits proposés : Produits frais, Produits secs, Produits laitiers, Conserves, Viandes et poissons, Fruits et légumes, Produits surgelés, Alimentation pour animaux, Produits d'entretiens, Produits de puériculture, Produits d'hygiène, Autre (couches et lait infantile)

Consommation : Sur place en intérieur, Sur place en extérieur, À emporter, Livraison

Adapté aux régimes : Végétarien, Halal, Sans gluten, Sans sel, Casher, Sans lactose

Equipements disponibles : Four, Micro-ondes, Couverts, Bouilloire, Casseroles, Autre (plancha extérieure)

Adapté pour les âges : Moins de 6 mois, 6-12 mois, 12-18 mois, 18 mois et plus

Type de bon : Autre (carte rechargeable)

Modalités d'utilisation : valable dans les commerces partenaires du quartier, sur présentation d'une pièce d'identité

Choix libre dans les produits

S'adapte du mieux possible aux contraintes alimentaires de chacun.e

assemblage de plusieurs catégories, pour l'inventaire

⚠ Composite : aucune catégorie réelle ne déclare tous ces champs à la fois. Ce bloc existe pour qu'on n'oublie aucun affichage au moment de la migration. Il montre les treize champs du corps et les quatre familles de rendu : liste libre en italique, valeur unique traduite, cases cochées séparées par des virgules, et les trois phrases toutes faites.

Aucun service

Rien n'est affiché

place.services_all.length === 0

La section entière disparaît, titre compris.

Web-appPlaceServices + ServiceInfoSection8 états

routes/[lang]/places/[identifier]/components/PlaceServices.svelte

Des dépliants au lieu d'une liste à plat, et une règle qui change tout : un service qui n'a rien à montrer n'est pas affiché du tout. L'état le plus fréquent du site public — le titre seul — n'existe pas ici.

Un service sans détail n'est pas rendu

Rien n'est affiché

{#if description || hours || info.length || saturation || tempClosure}

⚠️ Anomalie : L'état le plus fréquent en base — un service dont seule la catégorie est renseignée — disparaît de la fiche. Le visiteur ne sait donc pas que la structure rend ce service. La liste des services de la CARTE de résultat, elle, l'affiche : les deux écrans de la même application ne disent pas la même chose. À trancher, et le site public a probablement raison de le montrer.

Premier service déplié, les autres repliés
Distribution alimentaire

Colis alimentaires à retirer sur place, sur présentation d'un justificatif de domicile.

Douche

Serviette et savon fournis.

isExpanded={index === 0}

Chaque service est un dépliant, et seul le premier est ouvert. Le site public les rend tous à plat. Sur un téléphone, le choix se comprend ; il suppose en revanche que l'ordre des services soit pertinent, ce que rien ne garantit.

Pas de catégorie parente dans le titre
Distribution alimentaire

Sans le préfixe « Alimentation : ».

$i18n.t(getCategoryTranslationKey(category)) — et rien de plus

Le site public préfixe certaines catégories du nom de leur parente : « Alimentation : Distribution alimentaire ». La web-app affiche la catégorie seule, en s'appuyant sur l'icône pour donner le contexte. Les catégories qui ne se comprennent pas seules deviennent donc ambiguës.

Saturation — deux pastilles, sans précision
⚠ Risque de saturation ⚠ Saturé

saturated.status === HIGH → error, MODERATE → warning, LOW → rien

Les mêmes trois niveaux et les mêmes deux pastilles que le site public. Mais la précision libre saisie par la structure — « plus de places jusqu'à la fin du mois » — n'est pas affichée : la pastille ne contient que la clé traduite. Une information perdue.

Fermeture temporaire du service

Ce service est fermé temporairement du 01/07/2026 au 31/08/2026

tempClosure → variant error si CURRENT, warning sinon

Filtrée à la construction : `buildServiceTempClosure` renvoie `null` si le statut n'est ni CURRENT ni INCOMING. Le cas passé, inatteignable côté site public par accident, est ici impossible par construction.

Description repliée au-delà de trois lignes
Distribution alimentaire

Colis alimentaires à retirer sur place, sur présentation d'un justificatif de domicile et d'une attestation de la mairie… Voir plus

<TextClamper linesNotClamped={3}>

Clés SEE_MORE et SEE_LESS. Le site public affiche la description entière. Sur un service très décrit, la web-app gagne en lisibilité et perd le contenu au premier coup d'œil.

Publics et modalités du service
  • Accueil exclusifpersonnes isolées
  • Sur inscription

service.differentPublics / differentModalities → buildServiceInfo

Même règle que le site public : les blocs n'apparaissent que si le service diffère de la structure. Et même gabarit que la fiche, via `ServiceInfoSection` — le seul écart est la taille du texte, plus petite.

Horaires du service
  • Mardi 14h à 17h
  • Jeudi 14h à 17h

service.differentHours → <ServiceHours>

Clé SPECIFIC_TIMETABLES, avec une icône d'horloge en en-tête. Seuls les jours ouverts apparaissent.

Champs spécifiques d'un service11 étatsWeb-app : rien

Dix-neuf champs propres à certaines catégories : produits proposés, régimes adaptés, équipements, tranches d'âge, type de bon. Six s'affichent dans le titre du service, treize dans son corps, et deux ne s'affichent jamais publiquement. Quatre familles de rendu, et trois règles particulières qui font disparaître des valeurs.

Site publicapp-display-specific-fields11 états

app/modules/place/components/display-specific-fields/

Ce qui est réécrit. Les conditions sont celles du code Angular.

Champs de titre — six champs
  • Distribution alimentaire : déjeunercanteensMealType
  • Cours de français : alphabétisationcourseType
  • Domiciliation : postaledomiciliationType
  • Bien-être : sophrologiewellnessActivityName, texte libre
  • Produits d'hygiène : protections périodiqueshygieneProductType
  • Activités sportives : boxeactivityName, texte libre

specificField ∈ CATEGORIES_SPECIFIC_FIELDS_PLACE_DISPLAYED.title

Ceux-là ne passent pas par le composant : ils sont collés au nom de la catégorie, après deux-points, et c'est la valeur qui est traduite puis mise en minuscules — pas d'étiquette. D'où l'absence de clé DISPLAY_ pour eux.

Texte libre — en italique

Liste des métiers : maraîchage, second de cuisine, agent d'entretien

specificField ∈ CATEGORIES_SPECIFIC_FIELDS_FIELD_TYPE.textarea

Saisi par la structure, restitué tel quel en italique. Concerne la liste des métiers et les modalités d'utilisation d'un bon.

Valeur unique — traduite

Type de bon : Ticket alimentaire

specificField ∈ CATEGORIES_SPECIFIC_FIELDS_FIELD_TYPE.enumType

Une seule valeur, dont la clé de traduction est la valeur elle-même en majuscules.

Cases cochées — séparées par des virgules

Adapté aux régimes : Végétarien, Halal, Sans gluten, Sans sel, Casher, Sans lactose

specificField ∈ CATEGORIES_SPECIFIC_FIELDS_FIELD_TYPE.checklist

Plusieurs valeurs possibles, jointes par une virgule sans espace avant. La liste peut être longue : les produits proposés comptent douze valeurs.

« Autre » et sa précision entre parenthèses

Produits proposés : Produits frais, Conserves, Autre (couches et lait infantile)

Type de bon : Autre (carte rechargeable)

valeur === OTHER && le champ de précision correspondant est rempli

Les trois champs de précision ne s'affichent jamais seuls : ils sont insérés en italique, entre parenthèses, juste après la valeur « Autre » qu'ils précisent. Ça vaut pour les produits proposés, les équipements et le type de bon.

Degré de choix — les deux valeurs qui parlent

Choix libre dans les produits

Choix dans les produits, accompagné d'une personne de la structure

degreeOfChoiceType ∈ {FREE_CHOICE, ACCOMPAGNIED_CHOICE}

Ce champ ne s'affiche pas comme « étiquette : valeur » mais comme une phrase toute faite, différente selon la valeur. Clés DISPLAY_FREE_CHOICE et DISPLAY_DEGREE_OF_CHOICE_TYPE + DISPLAY_ACCOMPAGNIED.

Régimes alimentaires — la seule valeur qui parle

S'adapte du mieux possible aux contraintes alimentaires de chacun.e

dietaryRegimesType === DietaryRegimesType.TRY_TO_ADAPT

Même principe : une phrase, pas une étiquette. Clés DISPLAY_TRY_TO_ADAPT + DIETARY_OBLIGATIONS.

Tranches d'âge du colis bébé

Adapté pour les âges : Moins de 6 mois, 6-12 mois, 12-18 mois, 18 mois et plus

specificField === 'babyParcelAgeType'

Quatre libellés écrits à la main dans le template, pas déduits des valeurs : « Moins de 6 mois », « 6-12 mois », « 12-18 mois », « 18 mois et plus ».

Valeurs renseignées mais jamais affichées

Rien n'est affiché

degreeOfChoiceType === NO_CHOICE, ou dietaryRegimesType ∈ {WE_ADAPT, CANNOT_ADAPT, DO_NOT_KNOW}

La structure a répondu, et sa réponse ne s'affiche pas. « Pas de choix dans les produits » et « ne s'adapte pas aux régimes » sont des informations utiles pour une personne qui cherche — elles sont pourtant taries à l'affichage. À rouvrir avec le produit : c'est une décision, pas un détail technique.

Deux champs réservés à la gestion

Rien n'est affiché

specificField ∈ {organicOriginProductType, nationalOriginProductType}

L'origine biologique et l'origine nationale des produits sont collectées et rangées dans une liste à part, affichée seulement dans les écrans de gestion de la fiche. Elles n'apparaissent nulle part sur le site public.

Champs de précision appelés pour rien

Rien n'est affiché

specificField ∈ {voucherTypePrecisions, otherProductTypePrecisions, availableEquipmentPrecisions}

⚠️ Anomalie : Ces trois champs figurent dans la liste des champs à afficher dans le corps du service, donc le composant est bien appelé pour eux — mais sa condition d'affichage les exclut systématiquement. Trois appels qui ne rendent jamais rien. Leur contenu est en réalité affiché par le champ parent, entre parenthèses. Sans importance visuelle, mais à ne pas recopier tel quel.

Web-appPas d'équivalent

Les dix-neuf champs spécifiques — produits proposés, régimes adaptés, équipements, tranches d'âge, type de bon — ne sont affichés nulle part. Et ce n'est pas faute d'avoir commencé : `lib/components/CategorySpecificFieldsIcon.svelte` existe, avec ses icônes par champ, et il n'est importé par AUCUN fichier du paquet. `categorySpecificFields` n'apparaît que dans les jeux de données de test ; le modèle réel ne le lit jamais. Un composant écrit, jamais monté. Pour une personne qui cherche un colis alimentaire sans gluten, c'est l'information décisive : à la migration, elle vient du site public, pas de la web-app.

Ce qui n'est pas ici

Dit explicitement, pour que l'absence se lise comme un choix et non comme un oubli. Ces éléments existent, mais ce sont des blocs de page plutôt que des composants à états — ils ont leur propre chantier.

Page de résultats

  • La barre de recherche et le fil des catégories
  • La carte et le curseur de rayon
  • La pagination
  • L'écran « aucun résultat » et les logos partenaires

Vue d'une fiche

  • Les informations pratiques : adresse, téléphone, courriel, réseaux
  • Les transports et la carte
  • Les photos et les points d'un itinéraire
  • Les contacts réservés aux professionnels