v3 : publication — un envoi refusé repart, un dépôt neuf reçoit l'historique

Douzième relecture (850f9c4 : ok), défauts anciens de publish.rs mis en
jeu par le nouveau dépôt :
- un commit que le dépôt n'a jamais reçu (premier envoi refusé, clé sans
  droit d'écriture) repart au passage suivant, au lieu d'une file marquée
  faite avec un dépôt vide ;
- un changement de remote vers un dépôt neuf et vide lui envoie
  l'historique ;
- au démarrage, ce qui attendait repart aussitôt (le délai croissant
  pouvait atteindre 6 h).
trustinWallet montre l'état de la publication (en attente, dernier échec,
dernier commit) et offre « Publier maintenant ».

Boucle : 69 tests Rust, svelte-check, 9 vitest, e2e 13/13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 21:30:52 +02:00
co-authored by Claude Opus 5.5
parent 850f9c46b0
commit 744d157b01
10 changed files with 208 additions and 15 deletions
+8
View File
@@ -316,6 +316,13 @@ export const en: Messages = {
'access.publication.remote': "Repository: {remote}",
'access.publication.key': "The server's deploy key. Add it with write access on this repository (Settings › Repository › Deploy keys): until then, publications wait.",
'access.publication.none': "Publishing is not configured on this server.",
'access.publication.pending.one': '{n} sending waiting',
'access.publication.pending.other': '{n} sendings waiting',
'access.publication.last': 'Last publication: {date} (commit {commit})',
'access.publication.never': 'Nothing has been published to this repository yet.',
'access.publication.error': 'Last failure: {error}',
'access.publication.now': 'Publish now',
'access.publication.queued': 'Publication started again.',
'access.denied': 'Access management is for trustinWallet administrators only.',
'soon.title': 'Coming soon',
@@ -344,6 +351,7 @@ export const en: Messages = {
'error.name_taken': 'This name is already taken. Choose another one, adding an initial for instance.',
'error.team_limits': 'A team link lasts 1 to 90 days, for 1 to 500 people.',
'error.groups': 'Choose at least one group.',
'error.publication_off': 'Publication is not configured on this server.',
'error.retry': 'Try again',
'common.cancel': 'Cancel',
+8
View File
@@ -316,6 +316,13 @@ export const es: Messages = {
'access.publication.remote': "Repositorio: {remote}",
'access.publication.key': "Clave de despliegue del servidor. Añádela con permiso de escritura en este repositorio (Configuración › Repositorio › Claves de despliegue): sin ella, las publicaciones esperan.",
'access.publication.none': "La publicación no está configurada en este servidor.",
'access.publication.pending.one': '{n} envío en espera',
'access.publication.pending.other': '{n} envíos en espera',
'access.publication.last': 'Última publicación: {date} (commit {commit})',
'access.publication.never': 'Todavía no se ha publicado nada en este repositorio.',
'access.publication.error': 'Último fallo: {error}',
'access.publication.now': 'Publicar ahora',
'access.publication.queued': 'Publicación relanzada.',
'access.denied': 'La gestión de accesos está reservada a los administradores de trustinWallet.',
'soon.title': 'Próximamente',
@@ -344,6 +351,7 @@ export const es: Messages = {
'error.name_taken': 'Este nombre ya está ocupado. Elige otro, añadiendo una inicial por ejemplo.',
'error.team_limits': 'Un enlace de equipo vale de 1 a 90 días, para 1 a 500 personas.',
'error.groups': 'Elige al menos un grupo.',
'error.publication_off': 'La publicación no está configurada en este servidor.',
'error.retry': 'Reintentar',
'common.cancel': 'Cancelar',
+8
View File
@@ -319,6 +319,13 @@ export const fr = {
'access.publication.remote': "Dépôt : {remote}",
'access.publication.key': "Clé de déploiement du serveur. Ajoute-la avec le droit d'écriture sur ce dépôt (Paramètres › Dépôt › Clés de déploiement) : sans elle, les publications attendent.",
'access.publication.none': "La publication n'est pas configurée sur ce serveur.",
'access.publication.pending.one': '{n} envoi en attente',
'access.publication.pending.other': '{n} envois en attente',
'access.publication.last': 'Dernière publication : {date} (commit {commit})',
'access.publication.never': "Rien n'a encore été publié sur ce dépôt.",
'access.publication.error': 'Dernier échec : {error}',
'access.publication.now': 'Publier maintenant',
'access.publication.queued': 'Publication relancée.',
'access.denied': 'La gestion des accès est réservée aux administrateurs de trustinWallet.',
'soon.title': 'À venir',
@@ -347,6 +354,7 @@ export const fr = {
'error.name_taken': 'Ce nom est déjà pris. Choisis-en un autre, en ajoutant une initiale par exemple.',
'error.team_limits': "Un lien d'équipe vaut de 1 à 90 jours, pour 1 à 500 personnes.",
'error.groups': 'Choisis au moins un groupe.',
'error.publication_off': "La publication n'est pas configurée sur ce serveur.",
'error.retry': 'Réessayer',
'common.cancel': 'Annuler',
+25 -1
View File
@@ -7,9 +7,14 @@
import { i18n, t, type Key } from '$lib/i18n/index.svelte';
import { Remote } from '$lib/load.svelte';
import { session } from '$lib/session.svelte';
import { toasts } from '$lib/toast.svelte';
interface Overview {
publication: { remote: string | null; publicKey: string | null };
publication: {
remote: string | null;
publicKey: string | null;
status?: { pending: number; lastError: string | null; lastPublishedAt: string | null; lastCommit: string | null };
};
groups: { slug: string; name: string; policy: string; hidden: boolean; members: { identityId: string; grantedAt: string }[] }[];
identities: { id: string; kind: 'g1' | 'invite' | 'dev'; displayName: string; g1Member: boolean; admin: boolean; lastSeenAt: string | null; teamLink: string | null }[];
invitations: { id: string; label: string; admin: boolean; groups: string[]; expiresAt: string }[];
@@ -57,6 +62,13 @@
});
}
async function publishNow() {
await act(async () => {
await api.post('/access/publication/retry');
toasts.show(t('access.publication.queued'));
});
}
// A new sign-in link for an invited person (another device, a lost session).
async function relink(id: string, name: string) {
await act(async () => {
@@ -166,6 +178,15 @@
<div class="card block">
{#if data.value.publication.remote}
<p class="small">{t('access.publication.remote', { remote: data.value.publication.remote })}</p>
{#if data.value.publication.status}
{@const st = data.value.publication.status}
<p class="small">
{#if st.lastPublishedAt}{t('access.publication.last', { date: i18n.date(st.lastPublishedAt), commit: (st.lastCommit ?? '').slice(0, 7) })}{:else}{t('access.publication.never')}{/if}
{#if st.pending} · {i18n.plural('access.publication.pending', st.pending)}{/if}
</p>
{#if st.pending && st.lastError}<p class="small faint err">{t('access.publication.error', { error: st.lastError })}</p>{/if}
{#if st.pending}<button class="btn small" type="button" onclick={publishNow}>{t('access.publication.now')}</button>{/if}
{/if}
{#if data.value.publication.publicKey}
<p class="small muted">{t('access.publication.key')}</p>
<code class="key">{data.value.publication.publicKey}</code>
@@ -277,6 +298,9 @@
padding: 0.6rem 0.8rem;
border-radius: var(--r-input);
}
.err {
word-break: break-word;
}
.fb .body {
white-space: pre-wrap;
}