Commit 3a4fa73a authored by Kourser's avatar Kourser
Browse files

Vérification des accès, mise en page, marques et illustrations

SIGNALER UN SOUCI REVENAIT À SE DÉCLARER PRÊT. Le champ « un souci à
signaler » voyageait AVEC la confirmation : le joueur qui écrivait « je
n'ai pas de son » puis appuyait sur Entrée se déclarait prêt du même
mouvement, et l'animation voyait une ligne verte. C'est l'inverse exact
de ce qu'une vérification d'accès existe pour faire remonter.

Deux gestes distincts, donc, et un quatrième état — REPORTED : il a
répondu, mais pour signaler. Il prime à l'affichage sur « en attente »,
parce qu'il appelle une action.

La sollicitation devient une FENÊTRE au milieu de l'écran. Elle
interrompt volontairement : elle demande une réponse avant le lancement,
et un bandeau en haut à droite se regarde sans se voir.

CHAQUE PARTIE DE L'ONGLET PRÉPARATION EST UN BLOC AUTONOME. Le filet
entre sections, posé la dernière fois, ne suffisait pas : la page restait
un ruban où l'on ne voyait pas où finissait un réglage. Sept blocs
encadrés, et un seul niveau de cadre.

Les en-têtes triables gardaient l'aspect natif du navigateur — fond gris,
bordure — au milieu d'un tableau qui n'en a aucun. Livrés sans style de
ma part la dernière fois.

UN MEMBRE DE LA CELLULE NE POUVAIT QU'ÊTRE RETIRÉ. La cause n'était pas
l'absence de bouton : la route de modification exigeait l'objet ENTIER,
nom compris, si bien qu'un PATCH corrigeant une fonction était refusé.
Un DTO partiel, et la chaîne vide efface au lieu d'écrire du vide.

LES MARQUES DE TIERS DISPARAISSENT de l'interface, ce que SECURITY.md
annonçait déjà pour les logos : X devient P, LinkedIn LinkedOut,
Instagram Instagroom, Le Figaro Le Figarii, Ouest-France Far-West France,
franceinfo InfoFrance:. Les monogrammes suivent — un « 𝕏 » est une marque
autant qu'un nom.

Les noms étaient recopiés dans cinq fichiers, dont deux tables mortes
qui ont divergé au premier renommage. Une source unique par famille,
comme `outletName` le faisait déjà pour la presse.

UN ARTICLE DE PRESSE PEUT PORTER UNE ILLUSTRATION, référencée comme
celles des publications sociales — jamais recopiée, et l'article survit à
la suppression de l'image.

Le parcours de kit accepte enfin qu'on AJOUTE ses propres variables : on
ne pouvait en définir une qu'après création, depuis la page Variables,
donc quand on y pensait.

Quatre essais de plus sur l'état d'une vérification.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent b48b2d9d
Loading
Loading
Loading
Loading
+11 −0
Changes for apps/api/prisma/migrations/20260903200000_news_image/migration.sql: 11 added lines, 0 removed lines.
Original line number Diff line number Diff line
-- Image d'illustration d'un article de presse simule.
--
-- Meme mecanique que les publications sociales : une reference vers une image
-- deja televersee dans l'exercice, jamais une copie. SET NULL a la suppression
-- de l'image — un article dont l'illustration disparait reste un article, et
-- perdre le texte parce qu'on a retire une photo serait absurde.
ALTER TABLE "NewsArticle" ADD COLUMN "imageId" TEXT;

ALTER TABLE "NewsArticle"
  ADD CONSTRAINT "NewsArticle_imageId_fkey"
  FOREIGN KEY ("imageId") REFERENCES "ExerciseImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+6 −0
Changes for apps/api/prisma/schema.prisma: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -854,6 +854,11 @@ model NewsArticle {
  category      String?
  source        String
  body          String
  /// Illustration facultative. Une REFERENCE vers une image deja televersee dans
  /// l'exercice, jamais une copie ; SET NULL a sa suppression, un article sans
  /// photo restant un article.
  imageId       String?
  image         ExerciseImage? @relation(fields: [imageId], references: [id], onDelete: SetNull)
  atExerciseSec Int
  createdAt     DateTime   @default(now())

@@ -887,6 +892,7 @@ model ExerciseImage {
  socialPosts  SocialPost[]
  chatMessages ChatMessage[]
  messages     Message[]
  newsArticles NewsArticle[]

  createdAt DateTime @default(now())

+25 −0
Changes for apps/api/src/engine/comcheck-state.spec.ts: 25 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -28,3 +28,28 @@ describe('comcheck-state', () => {
    expect(countConfirmed(entries)).toBe(2);
  });
});

describe('entryState — souci signalé', () => {
  it('distingue un souci signalé d’une simple attente', () => {
    // Le joueur A répondu : il a juste répondu « ça ne marche pas ».
    expect(entryState({ confirmedAt: null, note: 'Pas de son', lastSeenAt: new Date() })).toBe(
      'REPORTED',
    );
  });

  it('une confirmation l’emporte, même accompagnée d’une note', () => {
    // « Confirmé avec une remarque » reste confirmé : l'accès fonctionne.
    expect(
      entryState({ confirmedAt: new Date(), note: 'Le chat rame', lastSeenAt: new Date() }),
    ).toBe('CONFIRMED');
  });

  it('un souci signalé sans être jamais venu reste un souci', () => {
    // Impossible en pratique, mais l'ordre des règles doit être explicite.
    expect(entryState({ confirmedAt: null, note: 'Lien mort', lastSeenAt: null })).toBe('REPORTED');
  });

  it('une note vide ne vaut pas signalement', () => {
    expect(entryState({ confirmedAt: null, note: '   ', lastSeenAt: new Date() })).toBe('PENDING');
  });
});
+15 −4
Changes for apps/api/src/engine/comcheck-state.ts: 15 added lines, 4 removed lines.
Original line number Diff line number Diff line
/**
 * Verification des acces joueurs — logique pure et testable.
 *
 * Trois etats possibles pour un participant sollicite :
 * Quatre etats possibles pour un participant sollicite :
 * - CONFIRMED  : il a declare son acces fonctionnel ;
 * - REPORTED   : il a repondu, mais pour SIGNALER un souci ;
 * - NEVER_SEEN : il n'a jamais ouvert son lien (le probleme est en amont) ;
 * - PENDING    : il est deja venu mais n'a pas encore confirme.
 * - PENDING    : il est deja venu mais n'a pas encore repondu.
 *
 * La distinction NEVER_SEEN / PENDING est ce qui rend le tableau actionnable :
 * un lien jamais ouvert se resout en renvoyant le lien, pas en relancant la personne.
 * un lien jamais ouvert se resout en renvoyant le lien, pas en relancant la
 * personne.
 *
 * REPORTED a ete ajoute parce qu'un souci n'existait qu'ACCOMPAGNE d'une
 * confirmation : le joueur qui ecrivait « je n'ai pas de son » se declarait
 * pret du meme geste, et l'animation voyait une ligne verte. C'est exactement
 * l'information qu'une verification d'acces existe pour faire remonter.
 */

export type ComcheckEntryState = 'CONFIRMED' | 'NEVER_SEEN' | 'PENDING';
export type ComcheckEntryState = 'CONFIRMED' | 'REPORTED' | 'NEVER_SEEN' | 'PENDING';

export interface ComcheckEntryFacts {
  confirmedAt: Date | null;
  /** Souci signale par le joueur, avec ou sans confirmation. */
  note?: string | null;
  /** Derniere ouverture de l'espace joueur (null = jamais venu). */
  lastSeenAt: Date | null;
}

export function entryState(entry: ComcheckEntryFacts): ComcheckEntryState {
  if (entry.confirmedAt !== null) return 'CONFIRMED';
  // Un souci signale prime sur « en attente » : le joueur A repondu.
  if (entry.note?.trim()) return 'REPORTED';
  return entry.lastSeenAt === null ? 'NEVER_SEEN' : 'PENDING';
}

+12 −0
Changes for apps/api/src/engine/comcheck.controller.ts: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -24,6 +24,18 @@ export class ComcheckController {
  confirm(@CurrentPlayer() player: PlayerContext, @Body() dto: ConfirmComcheckDto) {
    return this.comcheck.confirm(player, dto);
  }

  /**
   * Signale un souci SANS se declarer pret.
   *
   * Deux gestes distincts, parce que ce sont deux reponses opposees : la note
   * voyageait avec la confirmation, si bien que decrire une panne revenait a
   * dire que tout allait bien.
   */
  @Post('report')
  report(@CurrentPlayer() player: PlayerContext, @Body() dto: ConfirmComcheckDto) {
    return this.comcheck.report(player, dto.note ?? '');
  }
}

/** Verification des acces cote animation : ouvrir, suivre, clore. */
Loading