尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
PWA de gestion des dettes entre amis. Chaque pote est un « compte » : un seul chiffre net par personne, et une timeline complete des mouvements (cf. Hasbni_CDC.pdf). Le mode demonstration (jeu de donnees local, persistance IndexedDB, aucune requete reseau) demande NEXT_PUBLIC_DEMO=1. En production, un build sans variables Supabase et sans ce drapeau echoue volontairement : une app deployee avec des potes fictifs ressemble a une app qui marche.查看完整 README ↗
PWA de gestion des dettes entre amis. Chaque pote est un « compte » : un seul chiffre net par
personne, et une timeline complete des mouvements (cf. Hasbni_CDC.pdf).
npm install
npm run dev # http://localhost:3000 — mode demonstration
Le mode demonstration (jeu de donnees local, persistance IndexedDB, aucune requete
reseau) demande NEXT_PUBLIC_DEMO=1. En production, un build sans variables Supabase et
sans ce drapeau echoue volontairement : une app deployee avec des potes fictifs ressemble
a une app qui marche.
supabase/schema.sql dans l'editeur SQL et l'executer. Ce fichier suffit :
il contient l'etat consolide (tables, contraintes, triggers, RPC, RLS, vue des soldes,
realtime). Les anciens patches sont archives dans supabase/patches/ et ne doivent
pas etre rejoues.cp .env.example .env.local puis renseigner NEXT_PUBLIC_SUPABASE_URL et
NEXT_PUBLIC_SUPABASE_ANON_KEY (Project Settings > API).npm run dev — les variables ne sont lues qu'au demarrage.Base deja en service ? Executer, dans l'ordre :
supabase/patch-06-durcissement.sql, supabase/patch-07-admin.sql,
supabase/patch-08-droits-et-groupes.sql, supabase/patch-09-pseudos-et-recherche.sql,
supabase/patch-10-pseudo-a-inscription.sql. Tous idempotents.
Pour verifier ce qui est reellement applique : coller supabase/diagnostic.sql
dans l'editeur SQL — lecture seule. Si une fonction existe en base mais que
l'app repond « Could not find the function public.… », c'est le cache de schema
de PostgREST : executer notify pgrst, 'reload schema';.
L'authentification est email + mot de passe (provider Email, actif par defaut). Pour tester a plusieurs sans boite mail, desactiver Authentication > Sign In / Providers > Email > Confirm email : les comptes sont alors utilisables immediatement.
Au premier login, le trigger on_auth_user_created cree automatiquement le profil avec le prenom
saisi a l'inscription.
| Ecran | Route | CDC |
|---|---|---|
| Accueil — liste des potes, totaux globaux, recherche/tri | / | 2.2 |
| Relation — timeline anti-chronologique + solde net sticky | /relation/[id] | 2.3 |
| Ajout de depense — repartition egale / personnalisee / par items | modale | 2.4 |
| Remboursement — saisie + confirmation bilaterale | modale | 2.5 |
| Groupes — membres, soldes, bouton « Simplifier » | /groupes | 2.6 / 2.7 |
| Activite — tous les mouvements, file de confirmation | /activite | — |
| Profil — compte, pseudo, potes, recherche, etat de synchro | /profil | — |
| Connexion / inscription — email + mot de passe | /login | 4 (flow 1) |
Une depense appartient a celui qui a paye. Lui seul peut l'annuler ou la corriger : si Youba paie pour moi, je ne peux pas effacer ma dette. La correction etait sinon une porte derobee — il suffisait de ramener le montant a 1 DA. Un groupe, lui, est supprimable par son createur ; ses depenses sont detachees, jamais supprimees, et aucun solde ne bouge.
Regles de gestion (CDC 3) : solde net sur mouvements confirmes, signe positif = l'autre me doit, statut « en attente » jusqu'a confirmation, annulation par ecriture inverse (jamais de suppression physique), DA arrondi a l'unite, reste d'arrondi au payeur.
src/lib/types.ts — modele calque sur le CDC 5.2.src/lib/ledger.ts — moteur de grand livre : eclatement bilateral, soldes, timeline,
simplification de dettes.src/lib/ops.ts — toute mutation est une operation serialisable (Op) appliquee par un
reducteur pur. C'est ce qui rend la saisie hors ligne possible : meme fonction pour l'etat
optimiste local et pour le rejeu.src/context/AuthContext.tsx — session Supabase (email + mot de passe), resolution du profil.src/context/AppContext.tsx — etat, file de synchronisation, temps reel.supabase/schema.sql)profiles, groups, group_members, expenses, expense_shares, settlements,
ledger_entries, audit_log.SUM(ledger_entries confirmes entre A et B).relation_balances). Le client ne charge
qu'une fenetre recente de mouvements, pour la timeline — pas tout l'historique.create_expense,
amend_expense) : jamais de depense sans ses parts, jamais de montant modifie sans que le
grand livre suive. Une correction solde l'ancienne position par des ecritures d'ajustement
puis en regenere des neuves — le journal reste strictement additif.can_share_with() exige un lien d'amitie ou un
groupe commun. Sans ca, connaitre un identifiant de profil suffisait a fabriquer une dette
contre son proprietaire.auth.users.email : c'est la cle
de recherche des demandes de pote, la laisser modifiable permettait de squatter une adresse.deleted_at, cles en RESTRICT.
Les soldes des potes restent justes.current_profile_id(), is_group_member(), shares_context() et
can_see_expense() sont SECURITY DEFINER pour eviter les recursions de politiques. On ne voit
que les relations et groupes dont on fait partie.settlements_confirm
n'autorise le passage a confirmed que par le beneficiaire (to_user).ledger_entries, settlements, expenses — les soldes se mettent a
jour tout seuls quand un pote saisit quelque chose.public/sw.js — app shell precachee, navigations en network-first avec repli /offline,
statiques Next en cache-first, reste en stale-while-revalidate.src/lib/idb.ts) : hors ligne, chaque operation part dans
IndexedDB. Le retour du reseau (evenement online ou Background Sync via le service
worker) la vide dans l'ordre, avec upserts idempotents et abandon des erreurs non rejouables.beforeinstallprompt) et indicateur hors ligne / synchro.Le service worker n'est enregistre qu'en production (npm run build && npm start) — en dev, le
cache des chunks entre en conflit avec le rechargement a chaud.
Palette dans tailwind.config.ts, utilitaires dans src/app/globals.css :
| Token | Valeur | Usage |
|---|---|---|
navy | #0B3A2B | texte, titres |
brand | #22A06B | CTA, etats actifs, focus |
ocean | #14724F | hover des CTA |
cream | #FFFAE6 | badges « en attente » |
mist | #E9FFF4 | base du fond |
frost | #A9FBD7 | milieu du degrade |
silver | #E4EEE8 | bordures |
credit / debit | #15A05F / #E5484D | il me doit / je lui dois |
Classes : .glass, .glass-sm, .glass-sidebar, .glass-nav, .blob-1/2/3.
Animations : variantes partagees dans src/lib/motion.ts.
Chacun a un pseudo public (@youba), choisi a l'inscription et modifiable
dans Profil. La disponibilite est verifiee pendant la frappe :
username_available est un exists sur l'index unique qui ne renvoie qu'un
booleen — pas de count, aucune ligne rapatriee, aucune donnee personnelle. La recherche se
fait par DEBUT de pseudo ou de nom, jamais par sous-chaine — et c'est le coeur
de sa tenue en charge : un index btree sait servir like 'abc%' en descendant
l'arbre, alors que like '%abc%' l'ignore et balaye toute la table, a chaque
caractere tape par chaque utilisateur.
Les garde-fous, cote serveur (search_profiles) : trois caracteres minimum,
dix resultats maximum, et ni email ni telephone dans la reponse — la recherche
est ouverte a tous, elle ne doit pas devenir un annuaire de coordonnees. Cote
client : anti-rebond de 300 ms et abandon de la requete precedente, pour qu'une
frappe rapide ne parte pas en rafale et qu'une reponse lente n'ecrase pas une
plus recente.
/adminApplication a part, servie par le meme deploiement mais ne partageant rien avec
l'app : ni fournisseur de session, ni etat, ni temps reel, ni service worker
(exclu dans public/sw.js). Les routes applicatives vivent dans le groupe
src/app/(app)/, la console dans src/app/admin/.
supabase/patch-07-admin.sql..env.local : SUPABASE_SERVICE_ROLE_KEY (Project Settings >
API) et ADMIN_SESSION_SECRET (openssl rand -base64 48)./admin.SUPABASE_SERVICE_ROLE_KEY n'est jamais prefixee NEXT_PUBLIC_. Elle
contourne la RLS : dans un bundle navigateur, elle donnerait la base complete.
src/lib/admin/server.ts ouvre sur import 'server-only' — un import depuis un
composant client fait echouer le BUILD.profiles : le declencheur guard_profile_columns ne fige qu'une liste nommee
de colonnes, et chacun peut ecrire sa propre ligne — un is_admin sur
profiles serait auto-attribuable.admin_* sont retirees a public, anon et
authenticated. Postgres accorde EXECUTE a PUBLIC par defaut : sans ce
revoke, ces fonctions security definer seraient un aspirateur a donnees
ouvert a tout compte connecte.httpOnly :
l'app garde la sienne dans localStorage, qu'un middleware ne peut pas lire.admin_audit_log — avant que la donnee soit renvoyee.| Ecran | Repond a |
|---|---|
| Pulse | Est-ce que quelque chose brule ? |
| Activation | Ou perd-on les nouveaux inscrits ? |
| Retention | Reviennent-ils la semaine suivante ? |
| Utilisateurs | Files de support : sans pote, bloque, en attente depuis 7 j |
| Sante du grand livre | SUM(ledger_entries) colle-t-il encore aux parts et remboursements ? |
| Moderation | Cadence anormale, taux d'annulation, montants inhabituels |
« Sante du grand livre » est la raison de construire cette console plutot que de tout confier a un outil tiers : aucun produit d'analytics ne peut verifier cet invariant, il demande la logique metier de Hasbni. Un ecart y est toujours un bug — declencheur SQL ou rejeu de la file hors ligne.
src/lib/analytics.ts envoie les evenements par lots vers /api/events, qui les
ecrit avec la cle de service. Deux regles tenues partout : jamais de montant
dans un evenement, seulement une tranche (<1k, 1-5k, 5-20k, 20k+) ; et
un evenement porte le nom de ce que la personne a fait, pas du composant touche.
Le profil est deduit du jeton, jamais du corps de la requete — sinon n'importe
qui pourrait attribuer son activite a quelqu'un d'autre.
Agregation journaliere : POST /api/admin/rollup avec l'en-tete
x-rollup-secret, a brancher sur un cron.
git push. C'est tout — rien a faire cote appareils, et surtout personne n'a a
supprimer puis reinstaller son raccourci.
Au retour au premier plan, l'app verifie s'il y a du neuf (au plus une fois par heure). Si oui, un bandeau « Nouvelle version disponible » apparait ; un tap bascule et recharge. Les donnees locales — instantane et file de synchronisation — sont conservees.
Quatre choses sont necessaires pour qu'une PWA deja installee se mette a jour, et il en manquait quatre :
/sw.js?v=<build>,
l'identifiant vient de next.config.ts). Sinon le navigateur compare un
fichier souvent identique et conclut qu'il n'y a rien de neuf — c'est ce qui
obligeait a reinstaller le raccourci.updateViaCache: 'none' a l'enregistrement : sans ca, sw.js lui-meme
peut etre servi depuis le cache HTTP pendant des heures.update() explicite au retour au premier plan. Une PWA installee sur
iPhone n'est jamais fermee, seulement suspendue : elle peut passer des
semaines sans jamais verifier.Les charges utiles React Server Components ne sont jamais mises en cache : elles decrivent le rendu des pages, et les servir depuis le cache reafficherait l'ancienne interface apres une mise a jour.
Si un appareil reste bloque malgre tout, /admin et l'app partagent le meme
domaine : ouvrir le site dans Safari (pas le raccourci) force une navigation
reseau et declenche la bascule.
npm run verify # types + lint + tests
npm run test:watch
Les tests couvrent le coeur comptable : repartition avec reste, signe et statut des soldes, idempotence du reducteur d'operations (rejouer une operation ne double jamais un montant), correction d'une depense, et simplification de groupe. La CI GitHub Actions rejoue tout plus le build.