From 91efbc8dea1dafaf3cf44c70aa2f0643f8e125ff Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sun, 13 Sep 2026 08:36:37 -0400 Subject: [PATCH] pruttivox: any handle may send a text; the mail badge gets room to breathe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The producer gate on /api/say provider "prutti" only let @prutti and admins through, and the piece told everyone else to sign in as an approved producer. The klokkentales score already describes the community lane — members send a text, Prutti hears every clip and can veto — so open it to any signed-in handle. A handle already implies a verified email, so that check goes too. The chat lane stays message-id-only. On the prompt curtain, MatrixChunky8 digits are 3×7 at rows 0–6 of their cell; the 9px unread badge drew them at +2 and left nothing underneath. Now 11px tall, two clear pixels each side, nudged a pixel higher. Co-Authored-By: Claude Fable 5.1 --- system/netlify/functions/say.js | 21 +++++++++++-------- .../aesthetic.computer/disks/prompt.mjs | 10 ++++++--- .../aesthetic.computer/disks/pruttivox.mjs | 4 ++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/system/netlify/functions/say.js b/system/netlify/functions/say.js index b0109b746b..8d74dcd7e7 100644 --- a/system/netlify/functions/say.js +++ b/system/netlify/functions/say.js @@ -188,9 +188,10 @@ async function generateElevenLabs(text, gender, set, scream) { }; } -// Pruttivox uses Prutti's consented IVC. Arbitrary text is deliberately -// producer-gated below; the public chat lane remains message-id-only in -// `netlify/functions/pruttivox.mjs`. +// Pruttivox uses Prutti's consented IVC. Arbitrary text is open to any +// signed-in handle — the community lane in marketing/klokkentales/SCORE.md, +// where Prutti hears every clip and can veto it. The anonymous chat lane +// remains message-id-only in `netlify/functions/pruttivox.mjs`. async function generatePrutti(text) { const voiceId = process.env.PRUTTI_ELEVENLABS_VOICE_ID; if (!voiceId || !process.env.ELEVENLABS_API_KEY) { @@ -233,13 +234,15 @@ async function generatePrutti(text) { } async function authorizePruttivox(event) { - const { authorize, handleFor, hasAdmin } = await import("../../backend/authorization.mjs"); + const { authorize, handleFor } = await import("../../backend/authorization.mjs"); const user = await authorize(event.headers); - if (!user?.sub || !user.email_verified) return null; + if (!user?.sub) return null; + // Any handle may send a text. A handle already implies a verified email + // (handle.mjs won't mint one otherwise), and it's logged with the render, + // so a clip can always be traced back to whoever asked for it. const handle = String(await handleFor(user.sub) || "").toLowerCase(); - const allowed = handle === "prutti" || await hasAdmin(user, "aesthetic"); - return allowed ? `@${handle}` : null; + return handle ? `@${handle}` : null; } // ── Jeffrey: Professional Voice Clone (PVC) ────────────────────────── @@ -385,7 +388,7 @@ exports.handler = async (event) => { const gender = body.voice?.split(":")[0]?.toLowerCase() || "neutral"; // Provider: "jeffrey" (default PVC), "openai", "google", "eleven", - // or producer-gated "prutti". + // or handle-gated "prutti". // Can be set via body.provider; falls back to Jeffrey for parity // with the `say` piece default. const provider = body.provider || "jeffrey"; @@ -413,7 +416,7 @@ exports.handler = async (event) => { return { statusCode: 403, headers, - body: JSON.stringify({ message: "Pruttivox is available to its approved producers." }), + body: JSON.stringify({ message: "Pruttivox needs a signed-in handle." }), }; } if (utterance.length > 1200) { diff --git a/system/public/aesthetic.computer/disks/prompt.mjs b/system/public/aesthetic.computer/disks/prompt.mjs index 34fe94d33c..30fe919052 100644 --- a/system/public/aesthetic.computer/disks/prompt.mjs +++ b/system/public/aesthetic.computer/disks/prompt.mjs @@ -6626,10 +6626,14 @@ function paint($) { $.line(ex + w - 1, ey, ex + (w >> 1), ey + (h >> 1)); if (label) { + // MatrixChunky8 digits are 3×7 and sit at rows 0–6 of the cell, so + // a 9-tall badge with the glyph at +2 left no room underneath. + // 11 tall gives the number two clear pixels above and below. const bx = ex + w - 3; - const by = ey - 4; - $.ink(hot ? [255, 60, 70] : [220, 30, 40]).box(bx, by, badgeW, 9); - $.ink(hot ? [255, 180, 185] : [255, 120, 130]).box(bx, by, badgeW, 9, "outline"); + const by = ey - 5; + const badgeH = 11; + $.ink(hot ? [255, 60, 70] : [220, 30, 40]).box(bx, by, badgeW, badgeH); + $.ink(hot ? [255, 180, 185] : [255, 120, 130]).box(bx, by, badgeW, badgeH, "outline"); $.ink(255, 240, 240).write( label, { x: bx + 3, y: by + 2 }, diff --git a/system/public/aesthetic.computer/disks/pruttivox.mjs b/system/public/aesthetic.computer/disks/pruttivox.mjs index bf71bf8857..974849a5ab 100644 --- a/system/public/aesthetic.computer/disks/pruttivox.mjs +++ b/system/public/aesthetic.computer/disks/pruttivox.mjs @@ -112,7 +112,7 @@ function paint({ wipe, ink, screen }) { let footer = "TAP TO SPEAK"; let color = [190, 160, 185]; if (status === "authorizing") { - footer = "CHECKING PRODUCER ACCESS"; + footer = "CHECKING YOUR HANDLE"; color = [255, 210, 100]; } else if (status === "speaking") { footer = `SPEAKING ${Math.min(chunkIndex + 1, chunks.length + 1)} / ${chunks.length + 1}`; @@ -121,7 +121,7 @@ function paint({ wipe, ink, screen }) { footer = "COMPLETE · TAP TO REPLAY"; color = [190, 255, 100]; } else if (status === "error") { - footer = "UNAVAILABLE · SIGN IN AS AN APPROVED PRODUCER"; + footer = "UNAVAILABLE · SIGN IN WITH A HANDLE"; color = [255, 110, 110]; } ink(...color).write(footer, { center: "x", screen, y: screen.height - 24 }); -- 2.51.2