feat: 2.14.0 — Eithan terminal toolbar, voice UX, Dross improvements framework
- Terminal renamed Eithan: mobile font A−/A+ (per-URL ttyd opts), same-origin xterm Copy/Paste buttons, scroll-to-live, touch-default 17px - Dross voice: no keyboard pop after transcribe (fine-pointer only focus), autogrow textarea to ~5 lines, live amplitude meter on the mic while recording - Dross improvements: propose_improvement tool (CSS layer, exfil-sanitized, owner-approved, per-improvement rollback/restore), public /improvements.css, Settings panel. External MCP registry unchanged (no tool leak). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
13
lib/db/migrations/030_improvements.sql
Normal file
13
lib/db/migrations/030_improvements.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- Dross improvements: versioned, owner-gated CSS-layer changes to the Void itself.
|
||||
-- Each row is one improvement; rollback/restore is a status flip — instant, reversible.
|
||||
CREATE TABLE dross_improvements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
summary TEXT NOT NULL,
|
||||
css TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'active', 'rolled_back', 'rejected')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
decided_at TIMESTAMPTZ,
|
||||
decided_by TEXT
|
||||
);
|
||||
CREATE INDEX dross_improvements_status ON dross_improvements(status);
|
||||
58
lib/db/repos/improvements.js
Normal file
58
lib/db/repos/improvements.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// Dross improvements — versioned CSS-layer changes with instant rollback.
|
||||
import { pool } from '../pool.js';
|
||||
const q = (text, params) => pool.query(text, params);
|
||||
|
||||
// Same exfil guards as elsewhere: an approved improvement still can't phone home
|
||||
// or pull remote CSS. Pure visual tweaks only.
|
||||
const BANNED = /url\s*\(|@import|@charset|expression\s*\(|behavior\s*:|javascript:/i;
|
||||
const MAX_CSS = 20_000;
|
||||
|
||||
export function validateCss(css) {
|
||||
if (typeof css !== 'string' || !css.trim()) return 'css required';
|
||||
if (css.length > MAX_CSS) return `css too large (max ${MAX_CSS} chars)`;
|
||||
if (BANNED.test(css)) return 'css may not use url()/@import/expression — visual tweaks only';
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function create({ summary, css }) {
|
||||
const { rows } = await q(
|
||||
`INSERT INTO dross_improvements (summary, css) VALUES ($1, $2) RETURNING *`,
|
||||
[String(summary).slice(0, 200), css]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function list() {
|
||||
const { rows } = await q(
|
||||
`SELECT id, summary, status, created_at, decided_at, length(css) AS css_len
|
||||
FROM dross_improvements ORDER BY created_at DESC LIMIT 100`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function get(id) {
|
||||
const { rows } = await q(`SELECT * FROM dross_improvements WHERE id = $1`, [id]);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
// pending→active (approve) · active→rolled_back · rolled_back→active (restore) · pending→rejected
|
||||
const TRANSITIONS = {
|
||||
approve: { from: ['pending'], to: 'active' },
|
||||
rollback: { from: ['active'], to: 'rolled_back' },
|
||||
restore: { from: ['rolled_back'], to: 'active' },
|
||||
reject: { from: ['pending'], to: 'rejected' },
|
||||
};
|
||||
|
||||
export async function transition(id, verb, actor) {
|
||||
const t = TRANSITIONS[verb];
|
||||
if (!t) return null;
|
||||
const { rows } = await q(
|
||||
`UPDATE dross_improvements SET status = $1, decided_at = now(), decided_by = $2
|
||||
WHERE id = $3 AND status = ANY($4) RETURNING *`,
|
||||
[t.to, actor ?? 'owner', id, t.from]);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export async function activeCss() {
|
||||
const { rows } = await q(
|
||||
`SELECT summary, css FROM dross_improvements WHERE status = 'active' ORDER BY created_at`);
|
||||
return rows.map((r) => `/* dross: ${r.summary.replace(/\*\//g, '')} */\n${r.css}`).join('\n\n');
|
||||
}
|
||||
Reference in New Issue
Block a user