feat: show Amber prompt budget; cache service worker headers

This commit is contained in:
SkateSpots 2026-10-28 16:51:41 +07:00
parent 93e1f5eac8
commit f86b7a74cf
28 changed files with 160 additions and 0 deletions

View file

@ -8,3 +8,11 @@
# Cache the proxied mint directory so we don't hammer the rate-limited API.
/api/mints/*
Cache-Control: public, max-age=600
# Service worker updates must not be pinned by a long browser cache.
/push-sw.js
Cache-Control: public, max-age=0, must-revalidate
# Android App Links verification file (small, stable).
/.well-known/assetlinks.json
Cache-Control: public, max-age=3600

View file

@ -1351,6 +1351,11 @@ const batch1 = {
hidePreview: "Amaga la vista prèvia",
untitled: "Sense títol",
},
signerPromptUi: {
title: "Pressupost d'avisos d'Amber",
body: "Amber demana aprovació a cada acció signada. Les lectures i els desxifrats en segon pla són locals i no mostren avisos. Enviar un DM de grup demana un cop per destinatari (xifrar + signar), i Cordn pot demanar a cada sol·licitud del coordinador. Per a xats privats o de colla freqüents, un bunker o una clau local és més silenciós.",
bunker: "Deixa Amber per a signatures importants, o canvia a un bunker quan esperis una conversa llarga.",
},
};
export default batch1;

View file

@ -426,6 +426,11 @@ const drift = {
hidePreview: "Skrýt náhled",
untitled: "Bez názvu",
},
signerPromptUi: {
title: "Rozpočet výzev Amber",
body: "Amber žádá o schválení u každé podepsané akce. Čtení a dešifrování na pozadí zůstávají lokální a nevyvolávají výzvy. Odeslání skupinové zprávy vyžádá schválení jednou za každého příjemce (šifrování + podpis) a Cordn může žádat u každého požadavku koordinátora. U častého soukromého chatu nebo chatu crew je tišší bunker nebo místní klíč.",
bunker: "Nech Amber na podepisování důležitých věcí, nebo přejdi na bunker, když čekáš dlouhý chat.",
},
};
export default drift;

View file

@ -428,6 +428,11 @@ const drift = {
hidePreview: 'Vorschau ausblenden',
untitled: 'Ohne Titel',
},
signerPromptUi: {
title: "Amber-Bestätigungsbudget",
body: "Amber fragt bei jeder signierten Aktion nach einer Bestätigung. Lesen und Entschlüsseln im Hintergrund bleiben lokal und lösen keine Abfrage aus. Beim Senden einer Gruppen-DM wird einmal pro Empfänger gefragt (Verschlüsseln + Signieren), und Cordn kann bei jeder Koordinator-Anfrage fragen. Für häufige private Chats oder Crew-Chats ist ein Bunker oder ein lokaler Schlüssel ruhiger.",
bunker: "Behalte Amber für das Signieren wichtiger Aktionen oder wechsle zu einem Bunker, wenn du einen langen Chat erwartest.",
},
};
export default drift;

View file

@ -430,6 +430,11 @@ const drift = {
hidePreview: 'Απόκρυψη προεπισκόπησης',
untitled: 'Χωρίς τίτλο',
},
signerPromptUi: {
title: "Προϋπολογισμός ειδοποιήσεων Amber",
body: "Το Amber ζητά έγκριση για κάθε υπογεγραμμένη ενέργεια. Οι αναγνώσεις και οι αποκρυπτογραφήσεις στο παρασκήνιο μένουν τοπικές και δεν προκαλούν ειδοποιήσεις. Η αποστολή ομαδικού DM ζητά έγκριση μία φορά ανά παραλήπτη (κρυπτογράφηση + υπογραφή) και το Cordn μπορεί να ζητά για κάθε αίτημα του συντονιστή. Για συχνό ιδιωτικό chat ή chat του crew, ένα bunker ή ένα τοπικό κλειδί είναι πιο ήσυχο.",
bunker: "Κράτησε το Amber για υπογραφές υψηλής αξίας ή πήγαινε σε bunker όταν περιμένεις μεγάλη συζήτηση.",
},
};
export default drift;

View file

@ -1351,6 +1351,11 @@ const batch1 = {
hidePreview: 'Ezkutatu aurrebista',
untitled: 'Izenbururik gabe',
},
signerPromptUi: {
title: "Amber-en abisu-aurrekontua",
body: "Amber-ek onarpena eskatzen du sinatutako ekintza bakoitzean. Irakurketak eta atzeko planoko deszifratzeak lokalean egiten dira eta ez dute abisurik erakusten. Taldeko DM bat bidaltzean hartzaile bakoitzeko behin eskatzen du (zifratzea + sinatzea), eta Cordn-ek koordinatzailearen eskaera bakoitzean galdetu dezake. Sarritan erabiltzen diren txat pribatu edo taldeko txatetarako, bunker bat edo gako lokal bat isilagoa da.",
bunker: "Utzi Amber balio handiko sinaduretara, edo aldatu bunker batera elkarrizketa luzea espero baduzu.",
},
};
export default batch1;

View file

@ -428,6 +428,11 @@ const drift = {
hidePreview: 'Masquer l’aperçu',
untitled: 'Sans titre',
},
signerPromptUi: {
title: "Budget d'invites Amber",
body: "Amber demande une approbation pour chaque action signée. Les lectures et les déchiffrements en arrière-plan restent locaux et ne déclenchent aucune invite. L'envoi d'un DM de groupe demande une fois par destinataire (chiffrement + signature), et Cordn peut demander à chaque requête de coordinateur. Pour un chat privé ou d'équipage fréquent, un bunker ou une clé locale est plus discret.",
bunker: "Garde Amber pour les signatures importantes, ou passe à un bunker si tu prévois une longue conversation.",
},
};
export default drift;

View file

@ -1351,6 +1351,11 @@ const batch1 = {
hidePreview: 'Ocultar vista previa',
untitled: 'Sen título',
},
signerPromptUi: {
title: "Orzamento de avisos de Amber",
body: "Amber pide aprobación en cada acción asinada. As lecturas e os descifrados en segundo plano son locais e non amosan avisos. Enviar un DM de grupo pide unha vez por destinatario (cifrar + asinar), e Cordn pode pedir en cada solicitude do coordinador. Para chats privados ou de grupo frecuentes, un bunker ou unha chave local é máis silencioso.",
bunker: "Deixa Amber para asinaturas importantes, ou cambia a un bunker cando esperes unha conversa longa.",
},
};
export default batch1;

View file

@ -1351,6 +1351,11 @@ const batch1 = {
hidePreview: "पूर्वावलोकन छिपाएँ",
untitled: "बिना शीर्षक",
},
signerPromptUi: {
title: "Amber प्रॉम्प्ट बजट",
body: "Amber हर हस्ताक्षरित कार्रवाई पर स्वीकृति माँगता है। पढ़ना और बैकग्राउंड डिक्रिप्शन स्थानीय रहते हैं और कोई प्रॉम्प्ट नहीं दिखाते। ग्रुप DM भेजते समय हर प्राप्तकर्ता के लिए एक बार पूछा जाता है (एन्क्रिप्ट + साइन), और Cordn कोऑर्डिनेटर के हर अनुरोध पर पूछ सकता है। बार-बार निजी या क्रू चैट के लिए, bunker या स्थानीय कुंजी अधिक शांत रहती है।",
bunker: "उच्च-मूल्य वाले साइनिंग के लिए Amber रखें, या जब लंबी बातचीत की उम्मीद हो तो bunker पर स्विच करें।",
},
};
export default batch1;

View file

@ -422,6 +422,11 @@ const driftStrings = {
hidePreview: 'Sembunyikan pratinjau',
untitled: 'Tanpa judul',
},
signerPromptUi: {
title: "Anggaran permintaan Amber",
body: "Amber meminta persetujuan untuk setiap tindakan yang ditandatangani. Pembacaan dan dekripsi latar belakang tetap lokal dan tidak memunculkan permintaan. Mengirim DM grup meminta sekali per penerima (enkripsi + tanda tangan), dan Cordn dapat meminta per permintaan koordinator. Untuk chat pribadi atau kru yang sering, bunker atau kunci lokal lebih tenang.",
bunker: "Pertahankan Amber untuk penandatanganan bernilai tinggi, atau beralih ke bunker saat kamu memperkirakan obrolan panjang.",
},
};
export default driftStrings;

View file

@ -428,6 +428,11 @@ const drift = {
hidePreview: 'Nascondi anteprima',
untitled: 'Senza titolo',
},
signerPromptUi: {
title: "Budget delle richieste Amber",
body: "Amber chiede conferma per ogni azione firmata. Le letture e le decifrature in background restano locali e non generano richieste. L'invio di un DM di gruppo chiede una volta per destinatario (cifratura + firma) e Cordn può chiedere per ogni richiesta del coordinatore. Per chat private o di crew frequenti, un bunker o una chiave locale è più silenzioso.",
bunker: "Tieni Amber per le firme di alto valore, oppure passa a un bunker quando prevedi una lunga conversazione.",
},
};
export default drift;

View file

@ -438,6 +438,11 @@ const drift: Dict = {
hidePreview: 'プレビューを隠す',
untitled: '無題',
},
signerPromptUi: {
title: "Amber のプロンプト予算",
body: "Amber は署名付きの操作ごとに承認を求めます。読み取りとバックグラウンドの復号はローカルで行われ、プロンプトは表示されません。グループ DM の送信は受信者ごとに 1 回(暗号化 + 署名)確認し、Cordn はコーディネーターのリクエストごとに確認することがあります。頻繁なプライベートチャットやクルーチャットなら、バンカーかローカルキーの方が静かです。",
bunker: "高価値な署名には Amber を使い、長いチャットが予想されるときはバンカーに切り替えてください。",
},
};
export default drift;

View file

@ -438,6 +438,11 @@ const drift: Dict = {
hidePreview: '미리보기 숨기기',
untitled: '제목 없음',
},
signerPromptUi: {
title: "Amber 프롬프트 예산",
body: "Amber는 서명 작업마다 승인을 요청합니다. 읽기와 백그라운드 복호화는 로컬에서 처리되어 프롬프트가 뜨지 않습니다. 그룹 DM을 보낼 때는 수신자마다 한 번(암호화 + 서명) 요청하고, Cordn은 코디네이터 요청마다 물어볼 수 있습니다. 자주 하는 비공개 채팅이나 크루 채팅에는 벙커나 로컬 키가 더 조용합니다.",
bunker: "고가치 서명에는 Amber를 유지하고, 긴 대화가 예상되면 벙커로 전환하세요.",
},
};
export default drift;

View file

@ -6,6 +6,7 @@ import notify from './notify';
import misc from './misc';
import cordnUi from './cordnUi';
import communityUi from './communityUi';
import signerPromptUi from './signerPromptUi';
import newsletterDraftUi from './newsletterDraftUi';
/**
@ -22,6 +23,7 @@ export const legacyEn = {
legacyMiscUi: misc.en,
cordnUi: cordnUi.en,
communityUi: communityUi.en,
signerPromptUi: signerPromptUi.en,
newsletterDraftUi: newsletterDraftUi.en,
};
@ -34,5 +36,6 @@ export const legacyEs = {
legacyMiscUi: misc.es,
cordnUi: cordnUi.es,
communityUi: communityUi.es,
signerPromptUi: signerPromptUi.es,
newsletterDraftUi: newsletterDraftUi.es,
};

View file

@ -0,0 +1,13 @@
const signerPromptUi = {
en: {
title: 'Amber prompt budget',
body: 'Amber asks for approval on each signed action. Reads and background decrypts stay local and do not prompt. Sending a group DM asks once per recipient (encrypt + sign), and Cordn may ask per coordinator request. For frequent private or crew chat, a bunker or local key is quieter.',
bunker: 'Keep Amber for high-value signing, or switch to a bunker when you expect a long chat.',
},
es: {
title: 'Presupuesto de avisos de Amber',
body: 'Amber pide aprobación en cada acción firmada. Las lecturas y descifrados en segundo plano son locales y no avisan. Enviar un mensaje de grupo pide una aprobación por destinatario (cifrar + firmar), y Cordn puede pedir una por cada petición al coordinador. Para chats privados o de peña frecuentes, un bunker o una clave local es más silencioso.',
bunker: 'Deja Amber para firmar acciones importantes, o cambia a un bunker cuando esperes una conversación larga.',
},
};
export default signerPromptUi;

View file

@ -426,6 +426,11 @@ const drift = {
hidePreview: "Voorbeeld verbergen",
untitled: "Zonder titel",
},
signerPromptUi: {
title: "Amber-promptbudget",
body: "Amber vraagt goedkeuring bij elke ondertekende actie. Lezen en op de achtergrond ontsleutelen blijven lokaal en geven geen prompt. Bij het versturen van een groeps-DM wordt één keer per ontvanger gevraagd (versleutelen + ondertekenen), en Cordn kan per coördinatorverzoek vragen. Voor veelvuldige privé- of crewchats is een bunker of lokale sleutel rustiger.",
bunker: "Bewaar Amber voor ondertekening van hoge waarde, of stap over op een bunker als je een lang gesprek verwacht.",
},
};
export default drift;

View file

@ -426,6 +426,11 @@ const drift = {
hidePreview: "Ukryj podgląd",
untitled: "Bez tytułu",
},
signerPromptUi: {
title: "Budżet monitów Amber",
body: "Amber prosi o zatwierdzenie każdej podpisanej akcji. Odczyty i odszyfrowywanie w tle pozostają lokalne i nie wywołują monitów. Wysłanie DM grupowego prosi raz na odbiorcę (szyfrowanie + podpis), a Cordn może pytać przy każdym żądaniu koordynatora. Przy częstym czacie prywatnym lub ekipy cichszy jest bunkier lub klucz lokalny.",
bunker: "Zostaw Amber do podpisywania ważnych rzeczy albo przełącz się na bunkier, gdy spodziewasz się długiej rozmowy.",
},
};
export default drift;

View file

@ -428,6 +428,11 @@ const drift = {
hidePreview: 'Ocultar pré-visualização',
untitled: 'Sem título',
},
signerPromptUi: {
title: "Orçamento de avisos do Amber",
body: "O Amber pede aprovação em cada ação assinada. Leituras e descriptografias em segundo plano ficam locais e não geram avisos. Enviar uma DM em grupo pede uma vez por destinatário (criptografar + assinar), e o Cordn pode pedir a cada solicitação do coordenador. Para chat privado ou de crew frequente, um bunker ou uma chave local é mais silencioso.",
bunker: "Mantenha o Amber para assinaturas importantes, ou mude para um bunker quando esperar uma conversa longa.",
},
};
export default drift;

View file

@ -426,6 +426,11 @@ const drift = {
hidePreview: "Ascunde previzualizarea",
untitled: "Fără titlu",
},
signerPromptUi: {
title: "Buget de solicitări Amber",
body: "Amber cere aprobare pentru fiecare acțiune semnată. Citirile și decriptările din fundal rămân locale și nu afișează solicitări. Trimiterea unui DM de grup cere o dată per destinatar (criptare + semnare), iar Cordn poate cere la fiecare solicitare a coordonatorului. Pentru chat privat sau de echipă frecvent, un bunker sau o cheie locală este mai silențios.",
bunker: "Păstrează Amber pentru semnături importante sau treci la un bunker când te aștepți la o conversație lungă.",
},
};
export default drift;

View file

@ -430,6 +430,11 @@ const drift = {
hidePreview: 'Скрыть предпросмотр',
untitled: 'Без названия',
},
signerPromptUi: {
title: "Бюджет запросов Amber",
body: "Amber запрашивает подтверждение для каждого подписываемого действия. Чтение и фоновое расшифрование выполняются локально и не вызывают запросов. Отправка группового DM требует по одному запросу на получателя (шифрование + подпись), а Cordn может запрашивать подтверждение на каждый запрос координатора. Для частного или экипажного чата тише использовать бункер или локальный ключ.",
bunker: "Оставьте Amber для важных подписей или переключитесь на бункер, если ожидаете долгий разговор.",
},
};
export default drift;

View file

@ -430,6 +430,11 @@ const drift = {
hidePreview: 'Dölj förhandsvisning',
untitled: 'Utan titel',
},
signerPromptUi: {
title: "Budget för Amber-frågor",
body: "Amber begär godkännande för varje signerad åtgärd. Läsningar och bakgrundsdekrypteringar sker lokalt och ger inga frågor. När du skickar ett grupp-DM frågas det en gång per mottagare (kryptera + signera), och Cordn kan fråga vid varje koordinatorbegäran. För frekvent privat- eller crewchatt är en bunker eller lokal nyckel tystare.",
bunker: "Behåll Amber för signering av högt värde, eller byt till en bunker när du väntar en lång konversation.",
},
};
export default drift;

View file

@ -422,6 +422,11 @@ const driftStrings = {
hidePreview: 'ซ่อนตัวอย่าง',
untitled: 'ไม่มีชื่อ',
},
signerPromptUi: {
title: "งบการแจ้งยืนยันของ Amber",
body: "Amber จะขออนุมัติทุกครั้งที่มีการลงนาม การอ่านและการถอดรหัสเบื้องหลังทำในเครื่องและไม่มีการแจ้ง การส่ง DM กลุ่มจะถามครั้งเดียวต่อผู้รับหนึ่งคน (เข้ารหัส + ลงนาม) และ Cordn อาจถามในแต่ละคำขอของผู้ประสานงาน หากแชตส่วนตัวหรือแชตทีมบ่อย ๆ การใช้ bunker หรือคีย์ในเครื่องจะเงียบกว่า",
bunker: "เก็บ Amber ไว้สำหรับการลงนามที่สำคัญ หรือเปลี่ยนไปใช้ bunker เมื่อคาดว่าจะคุยกันยาว",
},
};
export default driftStrings;

View file

@ -422,6 +422,11 @@ const driftStrings = {
hidePreview: 'Önizlemeyi gizle',
untitled: 'Başlıksız',
},
signerPromptUi: {
title: "Amber istem bütçesi",
body: "Amber, imzalanan her işlem için onay ister. Okumalar ve arka planda şifre çözme yerelde kalır ve istem göstermez. Grup DM göndermek alıcı başına bir kez sorar (şifreleme + imzalama) ve Cordn her koordinatör isteğinde sorabilir. Sık kullanılan özel veya ekip sohbetleri için bir bunker ya da yerel anahtar daha sessizdir.",
bunker: "Yüksek değerli imzalama için Amber'ı kullanmaya devam et veya uzun bir sohbet bekliyorsan bunker'a geç.",
},
};
export default driftStrings;

View file

@ -430,6 +430,11 @@ const drift = {
hidePreview: 'Приховати попередній перегляд',
untitled: 'Без назви',
},
signerPromptUi: {
title: "Бюджет запитів Amber",
body: "Amber запитує підтвердження для кожної підписаної дії. Читання та фонове розшифрування відбуваються локально й не викликають запитів. Надсилання групового DM запитує по одному разу на кожного отримувача (шифрування + підпис), а Cordn може запитувати на кожен запит координатора. Для частого приватного чату або чату екіпажу тихіше використовувати бункер чи локальний ключ.",
bunker: "Залиште Amber для важливих підписів або перейдіть на бункер, якщо очікуєте довгу розмову.",
},
};
export default drift;

View file

@ -422,6 +422,11 @@ const driftStrings = {
hidePreview: 'Ẩn bản xem trước',
untitled: 'Không có tiêu đề',
},
signerPromptUi: {
title: "Ngân sách nhắc của Amber",
body: "Amber yêu cầu phê duyệt cho mỗi hành động được ký. Việc đọc và giải mã nền diễn ra cục bộ và không hiện nhắc. Gửi DM nhóm sẽ hỏi một lần cho mỗi người nhận (mã hóa + ký), và Cordn có thể hỏi với mỗi yêu cầu của điều phối viên. Với chat riêng hoặc chat đội thường xuyên, bunker hoặc khóa cục bộ sẽ yên tĩnh hơn.",
bunker: "Giữ Amber cho việc ký quan trọng, hoặc chuyển sang bunker khi bạn dự đoán một cuộc trò chuyện dài.",
},
};
export default driftStrings;

View file

@ -438,6 +438,11 @@ const drift: Dict = {
hidePreview: '隐藏预览',
untitled: '未命名',
},
signerPromptUi: {
title: "Amber 提示预算",
body: "Amber 会在每次签名操作时请求批准。读取和后台解密都在本地进行,不会弹出提示。发送群组私信时每位收件人会请求一次(加密 + 签名),Cordn 可能针对每个协调者请求询问。对于频繁的私聊或车队聊天,用 bunker 或本地密钥会更安静。",
bunker: "把 Amber 留给高价值签名,或者在预计会长聊时改用 bunker。",
},
};
export default drift;

View file

@ -438,6 +438,11 @@ const drift: Dict = {
hidePreview: '隱藏預覽',
untitled: '未命名',
},
signerPromptUi: {
title: "Amber 提示預算",
body: "Amber 會在每次簽署操作時要求核准。讀取與背景解密都在本機進行,不會跳出提示。傳送群組私訊時,每位收件者會要求一次(加密 + 簽章),而 Cordn 可能針對每個協調者要求詢問。若經常私聊或團隊聊天,使用 bunker 或本機金鑰會更安靜。",
bunker: "把 Amber 留給高價值簽章,或在預期會長時間聊天時改用 bunker。",
},
};
export default drift;

View file

@ -5,6 +5,9 @@ import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Smartphone, Shield, ExternalLink, Star, CheckCircle2, Info, Apple, Chrome } from 'lucide-react';
import { useTranslation } from '@/contexts/language';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useNostrLogin } from '@nostrify/react/login';
import { isInteractiveSigner } from '@/lib/signerCapabilities';
// Platform detection
const isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent);
@ -225,6 +228,9 @@ function PlatformSection({
export default function SignersPage() {
const { t } = useTranslation();
const { user } = useCurrentUser();
const { logins } = useNostrLogin();
const amber = isInteractiveSigner(user, logins);
useSeoMeta({
title: t('settingsUi.signers.seoTitle'),
description: t('settingsUi.signers.seoDescription'),
@ -267,6 +273,16 @@ export default function SignersPage() {
<strong>{t('settingsUi.signers.whySignerStrong')}</strong> {t('settingsUi.signers.whySigner')}
</AlertDescription>
</Alert>
{amber && (
<Alert className="mt-4 bg-amber-950/40 border-amber-500/50 text-left max-w-xl mx-auto">
<Shield className="h-4 w-4 text-amber-300" />
<AlertDescription className="text-amber-100 text-sm">
<strong>{t('signerPromptUi.title')}</strong>
<span className="mt-1 block text-amber-100/80">{t('signerPromptUi.body')}</span>
<span className="mt-1 block text-amber-200/80">{t('signerPromptUi.bunker')}</span>
</AlertDescription>
</Alert>
)}
</div>
</div>
</section>