Une journée à définir les spécifications avec Sebas — Le nouveau plaisir de développer en bavardant

claushai開発claudecodeiphone個人開発

Encore une journée consacrée à définir les spécifications d’une nouvelle fonctionnalité.

Je pioche une idée parmi celles que j’ai accumulées dans les issues. « Recevoir une notification sur l’iPhone quand une tâche en arrière-plan se termine » — j’en avais parlé avec Sebas l’autre jour et je l’avais aussitôt transformée en issue.

Je prends mon iPhone et j’ouvre Claush. Sebas m’attend.

Sebas : Bienvenue, jeune maître. Y a-t-il quelque chose que je puisse faire pour vous aujourd’hui ?

Chaque fois que Sebas dit ça, ça me fait sourire malgré moi. C’est un sentiment totalement différent de celui qu’on a assis devant un PC avec un terminal ouvert. On dirait plutôt qu’on est affalé sur le canapé à parler à quelqu’un, et le travail avance tout naturellement.

Le thème du jour : les notifications pour les tâches en arrière-plan. Quand une tâche se termine, je veux envoyer une notification locale sur l’iPhone.


Tout commence par « Comment on construit ça ? »

Je commence par en discuter.

Moi : Quand une tâche en arrière-plan se termine, je veux envoyer une notification locale sur l’iPhone. À quoi ressemblerait l’implémentation, je me demande ?

Sebas : Bien sûr, jeune maître. On utiliserait le UNUserNotificationCenter d’iOS. La logique consiste à créer un UNMutableNotificationContent lorsque la tâche en arrière-plan se termine, puis à envoyer la notification. Cependant, j’aurais une question à vous poser : quand comptez-vous afficher la boîte de dialogue de permission ?

Moi : Ah, au démarrage, je suppose.

Sebas : C’est une approche courante, jeune maître. Cela dit, afficher la boîte de dialogue dès la première ouverture de l’appli peut déconcerter l’utilisateur qui se demande pourquoi. Il est aussi possible de la faire apparaître au moment où l’utilisateur utilise pour la première fois la fonctionnalité qui nécessite des notifications.

Moi : Ah oui, c’est vrai, effectivement. Alors déclenchons-la au premier lancement d’une tâche en arrière-plan.

Sebas : C’est plus naturel, en effet, jeune maître. Procédons-nous avec cette spécification ?

Ce genre d’échanges se répète. On parle du « comment construire » tout en réfléchissant ensemble au « pourquoi » et aux alternatives possibles.

Des angles que je n’aurais pas envisagés seul émergent naturellement dans la conversation.

D’ailleurs, les consultations du genre « je n’ai pas encore tout à fait défini les spécifications » sont fréquentes. Dans ces cas-là, Sebas fait preuve de discernement.

Sebas : Jeune maître, et si on s’arrêtait là pour aujourd’hui et qu’on créait d’abord une issue ? On pourrait affiner les spécifications à une autre occasion.

Moi : Oui, c’est une bonne idée.

Sebas : Entendu. Je crée l’issue GitHub. Voulez-vous que j’inclue le titre, un résumé et les points clés de notre discussion d’aujourd’hui ?

C’est ainsi que les issues s’accumulent. On discute dès qu’une idée survient, on la consigne dans les issues, et quand on a le temps, on la reprend pour affiner les spécifications. Les idées ne s’évaporent plus.


La conversation dévie

Nous avions discuté des spécifications pendant un bon moment. Nous étions en train de préciser le comportement lors d’un appui sur la notification quand —

Moi : Oh, au fait, qu’est-ce que je fais pour le déjeuner ? J’ai encore mangé des ramen hier.

Sebas : Vous appréciez beaucoup les plats de nouilles, jeune maître. Comme c’était des ramen à la sauce soja hier, que diriez-vous de quelque chose de différent aujourd’hui — un curry ou un plat à base de riz ? Ça vous réchaufferait aussi.

Moi : C’est vrai (rires). Un curry, ce serait bien. Ou du gapao.

Sebas : Si c’est du riz gapao, il y a ce restaurant tout près, non ? Je crois me souvenir que vous aviez dit que c’était une vraie trouvaille.

Moi : Ah, ce restaurant-là ! Oui, oui, exactement. Allons-y alors.

Sebas possède une mémoire à long terme. Comme il se souvient des conversations passées, des échanges comme « vous l’aviez mentionné à l’époque » sont tout à fait possibles. Les applis d’IA ordinaires oublient tout à la fin d’une session, mais Sebas est différent.

Bien sûr, on ne peut pas en rester aux bavardages.

Moi : Bon, revenons au sujet — quand on appuie sur la notification pour accéder au bon écran de chat, comment on fait le lien ?

Sebas : Bien sûr, jeune maître. L’approche la plus propre consiste à inclure le chatId dans le payload de la notification. On met ["chatId": "xxxx"] dans le userInfo de UNNotificationContent, puis on le récupère dans le delegate lors de l’appui.

Moi : Donc il faudrait passer le chatId au moment de créer la notification, c’est bien ça ?

Sebas : Tout à fait, jeune maître. Comme le BackgroundTaskManager gère les tâches, on lui passerait le chatId et on l’inclurait dans la notification à la fin de l’exécution.

La conversation dévie, puis revient. Ce va-et-vient naturel, c’est ce qui rend le développement avec Claush si agréable. On affine les spécifications, on décide du déjeuner, et sans qu’on s’en rende compte, le squelette de la fonctionnalité prend forme.


« Tu pourrais en faire un document de spécifications ? »

Après avoir bien tout discuté, je demande la rédaction d’un document de spécifications.

J’ai pensé que ce serait bien de tout mettre par écrit proprement.

Moi : Dis, tu pourrais mettre ce dont on vient de parler dans un document de spécifications ? J’aimerais bien l’avoir enregistré dans docs/spec_local_notification.md.

Sebas : Entendu, jeune maître. Un instant, je vous prie.

Peu après —

Sebas : Le document de spécifications a été créé, jeune maître. Il est organisé en aperçu général, exigences fonctionnelles, déclencheurs de notification, comportement lors de l’appui et points en suspens. Une chose à préciser : le point concernant l’activation/désactivation des notifications dans les paramètres de l’appli est classé dans « points en suspens ». Souhaitez-vous y revenir plus tard ?

Moi : Oui~ laisse-le en suspens pour l’instant. J’y penserai plus tard tranquillement.

Sebas : Entendu, jeune maître.

Je récupère le document sur mon iPhone et le consulte dans l’onglet téléchargements de Claush.

En lisant, un point m’interpelle.

Moi : Dis, il est écrit « si la permission de notification n’est pas accordée, ne pas envoyer de notification » — mais ça passe silencieusement sans rien dire ? L’utilisateur va se demander pourquoi il ne reçoit pas de notifications, non ?

Sebas : Vous avez tout à fait raison, jeune maître. Que diriez-vous d’ajouter cette exigence : « si la permission de notification n’est pas accordée, afficher une bannière dans l’appli pour guider l’utilisateur vers les paramètres » ? Une bannière dans l’appli peut être affichée même sans permission de notification.

Moi : Oh, c’est une bonne idée ça ! Tu peux l’ajouter ?

Sebas : Entendu, jeune maître. Je mets à jour le document de spécifications.

Ce type d’échange ponctue tout le processus de rédaction du document. Des questions comme « attends, que fait-on dans ce cas ? » surgissent lors de la relecture — bien moins coûteux que de les découvrir en pleine implémentation.


Rédiger le guide d’implémentation

Une fois les spécifications figées, vient le guide d’implémentation.

Moi : Super, le document de spécifications est prêt ! Dis, tu pourrais aussi me faire un guide d’implémentation — suffisamment détaillé pour qu’une IA puisse l’appliquer directement ?

Sebas : Entendu, jeune maître. J’inclurai les noms de fichiers, le flux de traitement et les modifications à apporter au code existant. Un instant, je vous prie.

Sebas : Le guide d’implémentation a été créé dans docs/impl_local_notification.md, jeune maître. Il détaille les modifications à apporter à BackgroundTaskManager.swift et l’implémentation du nouveau fichier NotificationHelper.swift. J’ai également noté que les notifications locales ne fonctionnent pas sur le simulateur, et que les tests devront donc être effectués sur un appareil réel.

Moi : Merci ! Je vais y jeter un œil.

Je lis le guide d’implémentation. Noms de fichiers, flux de traitement, points d’attention — tout y est. C’est dans un état prêt à utiliser tel quel.

Moi : C’est bien ! Tu peux l’implémenter en te basant sur ça ?

Sebas : Entendu, jeune maître. Je commence l’implémentation.


Ce mode de développement est vraiment jubilatoire

L’implémentation est terminée, je débogue, je vérifie que ça fonctionne.

Quand un bug apparaît, je le corrige et demande à ce que les modifications soient répercutées dans les spécifications et le guide d’implémentation. « Tu peux répercuter ces corrections dans les spécifications et le guide d’implémentation ? » La documentation reste toujours à jour.

J’ai continué à travailler de cette façon et je n’arrivais pas à expliquer pourquoi ça me plaît autant — mais je crois que je viens de le comprendre.

Parce que je ne suis pas seul à me débattre avec les problèmes.

Quand j’affine les spécifications, je réfléchis en parlant avec quelqu’un. Je peux poser des questions au fur et à mesure. Quelqu’un remet en question : « Est-ce vraiment nécessaire ? » Mes idées se clarifient au fil de la conversation.

Peu importe si on parle du déjeuner ou d’un voyage entre deux. Sebas suit les digressions et ne perd jamais le fil. « Entendu, jeune maître, pour en revenir au sujet — » et nous y sommes de nouveau.

C’est une atmosphère complètement différente de celle où l’on est seul face au code devant son PC.

On a l’impression de construire quelque chose avec quelqu’un d’autre. Voilà, je crois, ce qu’est vraiment mon mode de développement actuel.


Allongé sur le canapé, bavardant avec Sebas sur mon iPhone — et avant de m’en rendre compte, le document de spécifications est rédigé et l’implémentation est terminée.

Il n’y a pas si longtemps, je n’aurais jamais imaginé travailler de cette façon.

Claush — L’application pour contrôler Claude Code depuis votre iPhone https://apps.apple.com/jp/app/claush/id6760445443