05.02 — Les routes de l’Igoristan#

src/config/routes.ts#

const __ROOT = "/igoristan/";
const DASHBOARD = "dashboard";

const __ROUTES = {
  DONKEY_SAUSAGE_DETECTOR: "donkey-sausage-eater-detector",
  DASHBOARD_NESTED: `${DASHBOARD}/nested`,
  RANDOM_LOADERS: "random-loaders",
  SACRED_UPLOAD: "sacred-upload",
  RANDOM_ERROR: "random-error",
  CHAOTIC_FORM: "chaotic-form",
  CORSICAMON: "corsicamon",
  MADNESS: "madness",
  DASHBOARD,
  HOME: "",
} as const satisfies Routes;

const ROUTES = createRoutes(__ROUTES, __ROOT);
RouteURLRôleChaos volontaire
HOME/igoristan/Page d’accueil avec liens vers les autres pages, autoplay audio — 
RANDOM_LOADERS/igoristan/random-loadersLoaders pseudo-aléatoiresLoaders qui restent affichés un temps variable
SACRED_UPLOAD/igoristan/sacred-uploadFormulaire d’upload react-dropzone — 
DASHBOARD/igoristan/dashboardLogin (mot de passe figatellu), MFA OTP optionneluseAuth peut échouer 10% du temps même avec le bon mdp
DASHBOARD_NESTED/igoristan/dashboard/nestedPage protégée, requiert MFA — 
CORSICAMON/igoristan/corsicamon« Pokédex corse », 3 picks aléatoires via API key1/5 chance de lever Error('lol') artificiellement
RANDOM_ERROR/igoristan/random-errorPage d’erreur facticeTitre matché par ERROR_PAGE_REGEX côté tests
CHAOTIC_FORM/igoristan/chaotic-formFormulaire instableÉléments .catch-me-if-you-can apparaissent aléatoirement
MADNESS/igoristan/madnessHistoires alternées (Cors, ThisIsBastia)Rend l’une OU l’autre au hasard
DONKEY_SAUSAGE_DETECTOR/igoristan/donkey-sausage-eater-detectorDétecteur de sales siciliens de merde30% d’échec aléatoire (BSOD-style)

Page par page#

const links = [
  { href: ROUTES.RANDOM_LOADERS, label: "Random loaders" },
  { href: ROUTES.SACRED_UPLOAD, label: "Sacred upload" },
  { href: ROUTES.DASHBOARD, label: "Dashboard" },
  { href: ROUTES.CORSICAMON, label: "Corsicamon" },
  { href: ROUTES.RANDOM_ERROR, label: "Random Error" },
  { href: ROUTES.CHAOTIC_FORM, label: "Chaotic form" },
  { href: ROUTES.MADNESS, label: "Madness" },
  { href: ROUTES.DONKEY_SAUSAGE_DETECTOR, label: "Donkey Sausage Detector" },
];

home#

8 liens vers les autres pages + autoplay audio Angelus de Jérusalem (amen 🙏).

chaotic-form#

const ChaoticFormPage = () => (
  <Main>
    <div className="mx-auto max-w-2xl">
      <h1 ...>Sacred Corsican Registration</h1>
      <ChaoticForm />
      <p ...>"Blessed are the Corsicans, for they shall inherit the Mediterranean" - Napoleon 4:20</p>
      <BackToHome ... />
    </div>
  </Main>
);

<ChaoticForm /> inclut des éléments .catch-me-if-you-can qui apparaissent / disparaissent au hasard, exact cas d’usage du Watcher côté Ocarina (cf. ../02-ocarina/07-watcher.md, ../07-ocarina-example/09-watcher-catch-me.md).

corsicamon#

const CORSICADEX_API = 'https://tests-workers.vercel.app/api/corsicadex';

export default function PokemonPicker() {
  const [apiKey, setApiKey] = useState<string>('');
  ...

  const fetchRandomPokemons = useCallback(async () => {
    ...
    while (ids.length < 3) {
      const randomId = Math.floor(Math.random() * 8) + 1;
      if (!ids.includes(randomId)) ids.push(randomId);
    }

    try {
      if (randint(1, 5) === 1) {
        throw new Error('lol');                        // ◄── 1/5 chance d'échec artificiel
      }
      const promises = ids.map(async (id) => {
        const res = await fetch(`${CORSICADEX_API}?id=${id}`, {
          headers: { 'x-api-key': apiKey }
        });
        return res.json();
      });
      ...
    } catch (err) {
      setFetchError(true);
    }
  }, [apiKey]);
}
  1. Pick aléatoire de 3 IDs distincts entre 1 et 8.
  2. Échec artificiel 1/5 (« lol »).
  3. Fetch parallèle des 3 fiches via Promise.all.

Côté tests :

  • corsicamon_enter_api_key.py : entre l’API key dans l’UI.
  • corsicamon_main.py : draw, gestion des erreurs (« lol »), retries.

donkey-sausage-eater-detector#

const DonkeySausageEaterDetector = () => {
  const [state, setState] = useState<'loading' | 'success' | 'error'>('loading');

  useEffect(() => {
    const hasError = Math.random() < 0.3;                    // ◄── 30% d'échec
    const timeoutId = setTimeout(() => {
      document.title = formatPageTitle({
        pageTitle: hasError ? ERROR_PAGE_TITLE : PAGE_TITLE,
        errorCode: hasError ? '500' : undefined
      });
      setState(hasError ? 'error' : 'success');
    }, randint(1, 5) * 250 + 450);
    return () => clearTimeout(timeoutId);
  }, []);
  ...
};

Délai aléatoire (700-2200 ms), titre dynamique (avec/sans 500), composants alternés (ApprovedVisitorWelcomePage vs DisapprovedVisitorGetOutPage).

Côté Ocarina, ça déclenche :

  • Le hook on_failure côté act qui détecte ERROR_PAGE_REGEX (cf. ../07-ocarina-example/02-adapters.md).
  • La transformation en HttpErrorPageReachedError → catched par transient_errors → retry.
  • OU l’utilisation de match_page (lorsqu’elle est définie dans un scénario)

dashboard#

Cas particulier : voir 03-use-auth.md pour le détail du useAuth et de l’OTP.

Autres pages#

  • random-loaders : loaders qui mettent plus ou moins de temps à afficher un résultat.
  • random-error : page d’erreur volontaire avec composants ErrorCode, InternalErrorMsg.
  • madness : Cors et ThisIsBastia rendus alternativement.
  • sacred-upload : Dropzone + preview, log files uploads.

Le sitemap exclut /dashboard/**#

// vite.config.ts
const SITEMAP_EXCLUSIONS = ['**/igoristan/dashboard/**'] as const;
...
sitemap({
  sitemapGenerator: (entries) => entries.filter((e) => !minimatch(new URL(e.loc).pathname, SITEMAP_EXCLUSIONS[0])),
  ...
})

→ La page authentifiée n’apparaît pas dans le sitemap. Convention SEO standard pour les pages protégées.