diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index e10ef41..ad5506a 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -861,6 +861,18 @@ When the plugin is activated and the link already exists, show “This link alre ### Assistant outcome Added an authenticated duplicate-check request when the popup opens after loading the active page. Existing links now show the requested warning before submission; comment and tag editing plus plugin retriggering remain available. +### User +Display any errors that occur during posting. + +### Assistant outcome +Save responses now include sanitized plugin failure details, and the popup displays publishing errors alongside the save confirmation, including Mastodon HTTP or connection failures. + +### User +When pressing Save link, display “Link saved to ” and hide all other information. Refresh only when the plugin is opened again. + +### Assistant outcome +Added a terminal save state to the popup. After a successful save it displays the localized LinkLog server URL, hides the form, session, warnings, and header information, and performs no refresh until the popup is opened again. + ### User The plugin settings still show `emailLabel`; make that `email` as the title for the email field. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index 83b2c91..41918a2 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -149,7 +149,9 @@ 140. Change the title of the field "One-time password" to "One-time password (when configured)". 157. The plugin settings still show 'emailLabel'; make that 'email' as title for the email field. 163. When a URL with the same title is already in the database, warn the user, allow comment and hashtag changes, and retrigger plugins such as Mastodon reposting. +166. Display any errors that occur during posting. 164. When the plugin is activated and the link already exists, show "This link already exists. Comment and tags can be updated, and by submitting publishing will be retriggered." +165. When pressing save link the plugin should display "Link saved to " and hide all other information. It should only refresh when the plugin is opened again 141. Remove any leading and trailing spaces when entering fields in the settings page of the plugin. 142. Do a full security audit document in what you have done in detail in Security-audit.md 143. Address issue 1. and improve password storage diff --git a/XPI/unsigned/LinkLog-0.1.0.xpi b/XPI/unsigned/LinkLog-0.1.0.xpi index a124ad7..648866e 100644 Binary files a/XPI/unsigned/LinkLog-0.1.0.xpi and b/XPI/unsigned/LinkLog-0.1.0.xpi differ diff --git a/backend/app/api/links.py b/backend/app/api/links.py index 9d9b791..ce52458 100644 --- a/backend/app/api/links.py +++ b/backend/app/api/links.py @@ -76,7 +76,12 @@ def create_link_endpoint(payload: LinkCreate, response: Response, authorization: mark_mastodon_posted(record['id'], info['user_id'], mastodon_result.get('post_id')) if any(result.get('status') == 'failed' for result in plugin_results): logger.warning('One or more plugins failed for link_id=%s results=%s', record['id'], plugin_results) - return {**record, 'duplicate': duplicate} + plugin_errors = [ + {'plugin': result.get('plugin', 'unknown'), 'reason': result.get('reason', 'Plugin failed')} + for result in plugin_results + if result.get('status') == 'failed' + ] + return {**record, 'duplicate': duplicate, 'plugin_errors': plugin_errors} @router.put('/links/{link_id}') diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index ab80df6..3752474 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -454,6 +454,18 @@ def test_duplicate_link_check_is_authenticated_and_detects_existing_entry(): assert check.json()['exists'] is True +def test_link_save_reports_plugin_posting_errors(): + with patch('backend.app.api.links.plugin_manager.dispatch', return_value=[ + {'status': 'failed', 'plugin': 'mastodon', 'reason': 'HTTP 503: unavailable'}, + ]): + response = client.post('/api/links', headers=login_headers(), json={ + 'title': 'Plugin error report', + 'url': 'https://example.com/plugin-error-report', + }) + assert response.status_code == 201 + assert response.json()['plugin_errors'] == [{'plugin': 'mastodon', 'reason': 'HTTP 503: unavailable'}] + + def test_links_support_tags_and_tag_filtering(): headers = login_headers() response = client.post('/api/links', headers=headers, json={ diff --git a/webextension/_locales/de/messages.json b/webextension/_locales/de/messages.json index 09062ce..00ea489 100644 --- a/webextension/_locales/de/messages.json +++ b/webextension/_locales/de/messages.json @@ -40,9 +40,10 @@ "configureAndLogIn": {"message": "Konfiguriere die Backend-URL und melde dich zuerst an."}, "sessionExpired": {"message": "Sitzung abgelaufen. Authentifiziere dich in den Einstellungen erneut."}, "submissionFailed": {"message": "Senden fehlgeschlagen"}, - "linkSaved": {"message": "Link erfolgreich gespeichert"}, + "linkSaved": {"message": "Link auf $URL$ gespeichert.", "placeholders": {"url": {"content": "$1"}}}, "linkAlreadyExists": {"message": "Dieser Link existiert bereits. Kommentar und Tags wurden aktualisiert und die Veröffentlichung erneut ausgelöst."}, "duplicateLinkWarning": {"message": "Dieser Link existiert bereits. Kommentar und Tags können aktualisiert werden; beim Absenden wird die Veröffentlichung erneut ausgelöst."}, + "publishingErrors": {"message": "Fehler bei der Veröffentlichung: $ERRORS$", "placeholders": {"errors": {"content": "$1"}}}, "submissionFailedConnection": {"message": "Senden fehlgeschlagen. Überprüfe die Verbindung zum Backend."}, "loggedInAt": {"message": "$USERNAME$ ist bei $BACKEND$ angemeldet", "placeholders": {"username": {"content": "$1"}, "backend": {"content": "$2"}}}, "fillAllFields": {"message": "Fülle alle Felder aus"}, diff --git a/webextension/_locales/en-US/messages.json b/webextension/_locales/en-US/messages.json index c23f955..58cfcc4 100644 --- a/webextension/_locales/en-US/messages.json +++ b/webextension/_locales/en-US/messages.json @@ -123,7 +123,12 @@ "message": "Submission failed" }, "linkSaved": { - "message": "Link saved successfully" + "message": "Link saved to $URL$.", + "placeholders": { + "url": { + "content": "$1" + } + } }, "linkAlreadyExists": { "message": "This link already exists. Comment and tags were updated, and publishing was retriggered." @@ -131,6 +136,14 @@ "duplicateLinkWarning": { "message": "This link already exists. Comment and tags can be updated, and by submitting publishing will be retriggered." }, + "publishingErrors": { + "message": "Publishing errors: $ERRORS$", + "placeholders": { + "errors": { + "content": "$1" + } + } + }, "submissionFailedConnection": { "message": "Submission failed. Check your backend connection." }, diff --git a/webextension/_locales/es/messages.json b/webextension/_locales/es/messages.json index 1c81969..39654b4 100644 --- a/webextension/_locales/es/messages.json +++ b/webextension/_locales/es/messages.json @@ -40,9 +40,10 @@ "configureAndLogIn": {"message": "Configura la URL del servidor e inicia sesión primero."}, "sessionExpired": {"message": "La sesión ha caducado. Vuelve a autenticarte en la configuración."}, "submissionFailed": {"message": "Error al enviar"}, - "linkSaved": {"message": "Enlace guardado correctamente"}, + "linkSaved": {"message": "Enlace guardado en $URL$.", "placeholders": {"url": {"content": "$1"}}}, "linkAlreadyExists": {"message": "Este enlace ya existe. Se actualizaron el comentario y las etiquetas, y se volvió a activar la publicación."}, "duplicateLinkWarning": {"message": "Este enlace ya existe. Puedes actualizar el comentario y las etiquetas; al enviarlo se volverá a activar la publicación."}, + "publishingErrors": {"message": "Errores de publicación: $ERRORS$", "placeholders": {"errors": {"content": "$1"}}}, "submissionFailedConnection": {"message": "Error al enviar. Comprueba la conexión con el servidor."}, "loggedInAt": {"message": "$USERNAME$ ha iniciado sesión en $BACKEND$", "placeholders": {"username": {"content": "$1"}, "backend": {"content": "$2"}}}, "fillAllFields": {"message": "Rellena todos los campos"}, diff --git a/webextension/_locales/fr/messages.json b/webextension/_locales/fr/messages.json index d620f72..68b42fe 100644 --- a/webextension/_locales/fr/messages.json +++ b/webextension/_locales/fr/messages.json @@ -40,9 +40,10 @@ "configureAndLogIn": {"message": "Configurez l’URL du serveur et connectez-vous d’abord."}, "sessionExpired": {"message": "Session expirée. Reconnectez-vous dans les paramètres."}, "submissionFailed": {"message": "Échec de l’envoi"}, - "linkSaved": {"message": "Lien enregistré"}, + "linkSaved": {"message": "Lien enregistré sur $URL$.", "placeholders": {"url": {"content": "$1"}}}, "linkAlreadyExists": {"message": "Ce lien existe déjà. Le commentaire et les étiquettes ont été mis à jour et la publication a été relancée."}, "duplicateLinkWarning": {"message": "Ce lien existe déjà. Le commentaire et les étiquettes peuvent être mis à jour ; l’envoi relancera la publication."}, + "publishingErrors": {"message": "Erreurs de publication : $ERRORS$", "placeholders": {"errors": {"content": "$1"}}}, "submissionFailedConnection": {"message": "Échec de l’envoi. Vérifiez la connexion au serveur."}, "loggedInAt": {"message": "$USERNAME$ est connecté à $BACKEND$", "placeholders": {"username": {"content": "$1"}, "backend": {"content": "$2"}}}, "fillAllFields": {"message": "Veuillez remplir tous les champs"}, diff --git a/webextension/_locales/nl/messages.json b/webextension/_locales/nl/messages.json index 3378357..d580c14 100644 --- a/webextension/_locales/nl/messages.json +++ b/webextension/_locales/nl/messages.json @@ -40,9 +40,10 @@ "configureAndLogIn": {"message": "Configureer de backend-URL en log eerst in."}, "sessionExpired": {"message": "Sessie verlopen. Verifieer opnieuw in de instellingen."}, "submissionFailed": {"message": "Verzenden mislukt"}, - "linkSaved": {"message": "Koppeling opgeslagen"}, + "linkSaved": {"message": "Koppeling opgeslagen op $URL$.", "placeholders": {"url": {"content": "$1"}}}, "linkAlreadyExists": {"message": "Deze koppeling bestaat al. De opmerking en tags zijn bijgewerkt en publiceren is opnieuw gestart."}, "duplicateLinkWarning": {"message": "Deze koppeling bestaat al. De opmerking en tags kunnen worden bijgewerkt; na verzenden wordt publiceren opnieuw gestart."}, + "publishingErrors": {"message": "Publicatiefouten: $ERRORS$", "placeholders": {"errors": {"content": "$1"}}}, "submissionFailedConnection": {"message": "Verzenden mislukt. Controleer de verbinding met de backend."}, "loggedInAt": {"message": "$USERNAME$ is ingelogd op $BACKEND$", "placeholders": {"username": {"content": "$1"}, "backend": {"content": "$2"}}}, "fillAllFields": {"message": "Vul alle velden in"}, diff --git a/webextension/popup.js b/webextension/popup.js index c89ac95..3a538a4 100644 --- a/webextension/popup.js +++ b/webextension/popup.js @@ -65,6 +65,19 @@ function showSignedInState(user, backendUrl) { form.classList.remove('hidden'); } +function showSavedState(message) { + document.querySelector('header').classList.add('hidden'); + document.querySelector('.extension-footer').classList.add('hidden'); + authSession.hidden = true; + authSession.classList.add('hidden'); + authWarning.hidden = true; + authWarning.classList.add('hidden'); + form.classList.add('hidden'); + statusEl.textContent = message; + statusEl.classList.remove('hidden', 'error'); + statusEl.classList.add('success'); +} + async function updateFeedLink() { const settings = await browser.storage.local.get(['backendUrl', 'username', 'accessToken']); if (!settings.backendUrl || !settings.username || !settings.accessToken) return; @@ -209,7 +222,13 @@ async function handleSubmit(event) { } const result = await response.json(); - setStatus(result.duplicate ? t('linkAlreadyExists') : t('linkSaved'), Boolean(result.duplicate)); + const saveMessage = result.duplicate ? t('linkAlreadyExists') : t('linkSaved', backendUrl); + if (result.plugin_errors?.length) { + const errors = result.plugin_errors.map((error) => `${error.plugin}: ${error.reason}`).join(' '); + showSavedState(`${saveMessage} ${t('publishingErrors', errors)}`); + return; + } + showSavedState(saveMessage); } catch (error) { setStatus(t('submissionFailedConnection'), true); }