HIÜRD

Une place de marché de services locaux sur iOS, Android et le web, avec la dictée de job par IA et le pipeline de release mobile que j'ai construit autour d'EAS, GitHub Actions et Maestro.
- Rôle
- Product Engineer (contrat)HIURD LLC
- Période
- Depuis sept. 2025provisoire
Contexte et problème
HIÜRD est une place de marché de petits boulots et de services locaux publiée par HIURD LLC. Quelqu'un publie une tâche, les prestataires à proximité font une offre, et les paiements passent par PayVault. Le produit existe sous la forme d'une app iOS, d'une app Android et d'un site public sur hiurd.com.
Une place de marché à deux faces met deux utilisateurs très différents dans une seule base de code. Et une place de marché mobile ne fonctionne que si elle publie souvent. Chaque release doit franchir deux stores, chacun avec ses propres règles de revue, et chaque lien partagé depuis l'app doit ouvrir le bon écran, que l'app soit installée ou non.
J'ai rejoint le projet comme product engineer sous contrat pour construire l'app mobile, le site et la machinerie de release autour d'eux.
Ce qui est vérifié ici
Les fiches des stores et le site ont été vérifiés le 28 septembre 2026. Le pipeline, les décisions internes et la documentation de release n'ont aucune trace publique, donc cette page en parle en termes généraux et signale ce qui reste à confirmer.
Contraintes
Deux stores, deux processus de revue. Apple et Google ont chacun leurs délais et leurs changements de politique, et un build qui passe chez l'un peut être retenu chez l'autre.
Le backend était écrit par les ingénieurs backend, et j'intégrais leurs API REST. Le client devait donc tolérer les changements de contrat et gérer correctement son cache sur des réseaux mobiles peu fiables.
Et le processus de release ne pouvait pas dépendre d'un seul ordinateur portable. Les clés de signature et les identifiants des stores devaient être rangés là où l'équipe pouvait y accéder, et chaque étape devait être reproductible par quelqu'un d'autre que moi.
Mon rôle précis
Product Engineer, sous contrat. J'ai construit l'app mobile multiplateforme en React Native et TypeScript, conçu les écrans mobiles avec les parties prenantes produit, et développé puis maintenu le site public en Next.js. J'ai construit les composants UI réutilisables, intégré les API REST avec les ingénieurs backend, et ajouté une fonctionnalité assistée par IA, construite avec Claude, LangChain et LangGraph, qui permet à un utilisateur de dicter un job au lieu de le saisir.
J'ai aussi pris en charge de bout en bout la CI/CD mobile et le pipeline de publication sur les stores : builds EAS, workflows GitHub Actions, tests Maestro sur émulateurs, publication gérée (managed publishing) sur iOS et Android, incidents de conformité Google Play, et une documentation de release de plus de trente pages.
Décisions clés et compromis
Une app, deux rôles explicites
Les clients publient des jobs (Creators) et les travailleurs les réalisent (Providers). Les alternatives à un modèle de rôle explicite sont deux apps séparées, une par côté, ce qui veut dire deux fiches store par plateforme, deux pipelines et deux installations pour quiconque joue des deux côtés ; ou aucun modèle du tout, juste des vérifications if (user.isProvider) disséminées dans l'UI.
L'app est livrée en un seul build avec le rôle comme concept nommé. L'idée est que la navigation, les permissions et l'accès aux données lisent le rôle depuis un seul endroit plutôt que depuis des conditions éparpillées. Le coût : de la logique conditionnelle dans une seule base de code, et une discipline sur les endroits où les vérifications de rôle ont le droit d'exister. Je l'ai accepté en échange d'un build, d'un pipeline et d'une fiche par store.
Des clés React Query centralisées
Les clés de requête écrites à la main dans chaque appel finissent par diverger. Deux écrans orthographient la même clé un peu différemment, une mutation en invalide une et l'autre garde des données périmées. Le correctif paresseux consiste à tout invalider après chaque mutation, ce qui sur un réseau mobile veut dire des rafales de refetch ; le correctif lourd est un store global pour les données serveur, qui duplique le cache que React Query tient déjà.
J'ai centralisé les clés en un seul endroit. La forme habituelle est une fabrique de clés par domaine, hiérarchique pour qu'invalider un parent se propage à ses enfants. Une version générique :
export const jobKeys = {
all: ['jobs'] as const,
lists: () => [...jobKeys.all, 'list'] as const,
list: (filters: JobFilters) => [...jobKeys.lists(), filters] as const,
details: () => [...jobKeys.all, 'detail'] as const,
detail: (id: string) => [...jobKeys.details(), id] as const,
};Avec des clés de cette forme, déposer une offre peut appeler invalidateQueries({ queryKey: jobKeys.detail(id) }) et rafraîchir ce job et rien d'autre. Le compromis est un module à maintenir, de la vigilance en revue pour qu'aucune clé écrite à la main ne se glisse à nouveau, et la migration des appels qui existaient déjà.
Open Graph dynamique pour les liens universels
Les universal links sur iOS et les App Links sur Android exigent tous les deux que le domaine du site serve un fichier d'association et réponde à la même URL sur le web. Puisque cette URL doit de toute façon exister sur le site, ses métadonnées Open Graph sont générées par job, de sorte qu'un lien partagé montre ce job dans l'aperçu, ouvre l'app quand elle est installée, et atterrit sur une vraie page sinon.
Les alternatives étaient un seul aperçu statique pour tout le site, qui rend tous les jobs partagés identiques, ou un service de liens courts séparé, un système de plus à faire tourner. Le coût d'une route dynamique est que la génération se trouve sur le chemin de la requête, donc il lui faut du cache et un repli pour les jobs qui n'existent plus.
EAS, GitHub Actions et Maestro
EAS Build exécute les builds natifs dans le cloud, garde les certificats et clés de signature côté Expo et expose un profil de build par environnement ; EAS Submit envoie le résultat aux deux stores. L'alternative était Fastlane sur un Mac auto-hébergé : contrôle total, et on se charge soi-même des versions de Xcode, des renouvellements de certificats et de la machine. Le coût, ce sont des minutes de build hébergées et du temps de file d'attente ; en échange, personne ne touche la signature à la main.
GitHub Actions orchestre l'ensemble parce que le code vit déjà sur GitHub. Déclencher EAS depuis un portable lie chaque release à une machine et à une personne.
Maestro exécute les flows de bout en bout sur des émulateurs en CI. Ses flows sont en YAML, lisibles par une personne produit, et il pilote l'app depuis l'extérieur sans embarquer un framework de test dans le binaire, là où Detox a besoin d'un build de test dédié. Le coût est le temps de démarrage des émulateurs en CI et un peu d'instabilité autour des animations.
appId: com.example.app
---
- launchApp
- tapOn: 'Post a job'
- inputText: 'Fix a leaking tap'
- tapOn: 'Continue'
- assertVisible: 'Choose a category'Publication gérée plutôt que sortie à l'approbation
Les deux stores permettent de soumettre un build, de le faire passer en revue, et de le retenir jusqu'à ce qu'un humain appuie sur le bouton de publication. J'ai utilisé ce mode des deux côtés pour qu'iOS et Android puissent livrer la même version dans la même fenêtre. Publier à l'approbation est plus simple et produit un décalage de versions : une plateforme reçoit une fonctionnalité avant l'autre, et un rollback ne touche qu'une seule plateforme. Le coût est une étape manuelle par release et une règle claire sur qui la fait et quand.
Les incidents de conformité Google Play font partie du même travail : l'application des règles s'accompagne de délais de réponse courts, et l'équipe doit trouver les déclarations et l'accès au store sans attendre que je sois en ligne. C'est à ça que sert la documentation de release, plus de trente pages.
Ce que j'ai livré
L'app iOS, HIÜRD - Local Jobs & Services, sur l'App Store sous HIURD LLC. L'app Android, package com.hiurdapp, sur Google Play. Le site public hiurd.com en Next.js. Le pipeline de release, des profils de build EAS et des workflows GitHub Actions jusqu'aux flows Maestro et à la publication gérée sur les deux stores, avec sa documentation. Dans le produit : les rôles Creator et Provider, les clés React Query centralisées, les métadonnées Open Graph dynamiques pour les jobs partagés sur le site, la dictée de job par IA sur Claude, LangChain et LangGraph, les composants UI réutilisables et le design des écrans mobiles.
Résultats et chiffres
Chiffres publics, relevés le 28 septembre 2026 :
L'App Store affiche la première version le 29 septembre 2025 et la version 1.20.2 le 3 septembre 2026 ; Google Play montre une mise à jour le 2 septembre 2026. Les deux apps sont en ligne. Un numéro de version comme celui-là, onze mois après le lancement, est la seule trace publique de la cadence de release que le pipeline est là pour tenir.
Apple ne publie aucun compteur de téléchargements, et je n'ai ni durées de build ni taux de crash que je puisse sourcer, donc je n'en affiche aucun.
Ce que je ferais différemment
Ce que j'emporterais vers la prochaine app publiée sur les stores : des flows de bout en bout pour les parcours qui rapportent de l'argent, publier un job et déposer une offre, écrits avant la première soumission plutôt qu'après, et un document de release qui grandit à chaque release comme une liste de contrôle vivante. L'association de domaine pour les liens universels a aussi sa place dans le premier build, puisque l'ajouter plus tard signifie une release de plus à faire passer par les deux stores.