merge: testing → main (reconcile 2-week divergence)

This commit is contained in:
2026-04-28 18:36:00 -04:00
parent 499836c9e4
commit 862e4dbb31
1235 changed files with 160255 additions and 7996 deletions

View File

@@ -0,0 +1,214 @@
/* Synthetic Files — layered on DeckyFleet.css.
Adds: filter row of label+select pairs, the file table itself, the
right-side detail drawer, and a TRUNCATED chip for capped bodies. */
.synthetic-files-root .mono { font-family: var(--font-mono); }
.synthetic-files-root .info-banner {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border);
border-left: 3px solid var(--violet);
padding: 10px 14px;
font-size: 0.78rem;
line-height: 1.5;
}
.synthetic-files-root .info-banner em { color: var(--matrix); font-style: normal; }
/* Filter row — three label+select pairs. */
.synthetic-files-root .filters {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: flex-end;
}
.synthetic-files-root .filter-group { display: flex; flex-direction: column; gap: 4px; }
.synthetic-files-root .filter-group label {
font-size: 0.62rem;
letter-spacing: 1.5px;
color: var(--dim);
text-transform: uppercase;
}
.synthetic-files-root select.filter-input {
background: var(--bg-elev, rgba(0, 0, 0, 0.3));
border: 1px solid var(--border);
color: var(--text);
padding: 6px 10px;
font-family: inherit;
font-size: 0.8rem;
outline: none;
cursor: pointer;
min-width: 180px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.synthetic-files-root select.filter-input:focus {
border-color: var(--violet);
box-shadow: 0 0 0 1px var(--violet);
}
/* File table. */
.synthetic-files-root .files-table-wrap {
border: 1px solid var(--border);
background: var(--panel);
overflow-x: auto;
}
.synthetic-files-root .files-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
}
.synthetic-files-root .files-table thead tr {
border-bottom: 1px solid var(--border);
}
.synthetic-files-root .files-table th {
padding: 10px 12px;
text-align: left;
font-size: 0.62rem;
letter-spacing: 1.5px;
color: var(--dim);
font-weight: 600;
text-transform: uppercase;
}
.synthetic-files-root .files-table tbody tr {
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
cursor: pointer;
transition: background 0.1s;
}
.synthetic-files-root .files-table tbody tr:hover {
background: var(--matrix-tint-10, rgba(0, 255, 65, 0.04));
}
.synthetic-files-root .files-table td {
padding: 8px 12px;
vertical-align: middle;
}
.synthetic-files-root .files-table td.path { font-family: var(--font-mono); word-break: break-all; }
.synthetic-files-root .files-table td.cls.canary { color: var(--amber, #f59e0b); }
.synthetic-files-root .files-table td.hash {
font-family: var(--font-mono);
color: var(--dim);
}
.synthetic-files-root .files-table td.dim-time {
color: var(--dim);
font-variant-numeric: tabular-nums;
}
.synthetic-files-root .files-table .empty-row td {
padding: 24px;
text-align: center;
opacity: 0.5;
font-size: 0.78rem;
letter-spacing: 1px;
text-transform: uppercase;
}
/* Pagination row. */
.synthetic-files-root .pager {
display: flex;
align-items: center;
gap: 12px;
margin-top: 4px;
}
.synthetic-files-root .pager .page-counter {
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--dim);
letter-spacing: 1px;
}
/* Drawer — right-side detail panel. */
.synthetic-files-root .drawer-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: flex-end;
z-index: 1000;
}
.synthetic-files-root .drawer {
width: min(720px, 100%);
height: 100%;
background: var(--bg-color, #0d1117);
border-left: 1px solid var(--border);
padding: 24px;
overflow-y: auto;
color: var(--text);
}
.synthetic-files-root .drawer-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
gap: 16px;
}
.synthetic-files-root .drawer-eyebrow {
font-size: 0.62rem;
letter-spacing: 1.5px;
color: var(--dim);
text-transform: uppercase;
}
.synthetic-files-root .drawer-title {
font-family: var(--font-mono);
font-size: 0.95rem;
font-weight: 700;
margin-top: 4px;
word-break: break-all;
color: var(--matrix);
}
.synthetic-files-root .drawer-close {
background: none;
border: none;
color: var(--text);
cursor: pointer;
padding: 0;
}
/* Drawer meta grid — label / value rows. */
.synthetic-files-root .meta-grid {
display: grid;
grid-template-columns: 140px 1fr;
row-gap: 6px;
font-size: 0.85rem;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.synthetic-files-root .meta-grid .label {
color: var(--dim);
font-size: 0.62rem;
letter-spacing: 1.5px;
text-transform: uppercase;
align-self: center;
}
.synthetic-files-root .meta-grid .value-canary { color: var(--amber, #f59e0b); }
/* Body preview block. */
.synthetic-files-root .body-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 0.62rem;
letter-spacing: 1.5px;
color: var(--dim);
text-transform: uppercase;
}
.synthetic-files-root .body-pre {
font-family: var(--font-mono);
white-space: pre-wrap;
word-break: break-word;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
padding: 12px;
font-size: 0.78rem;
max-height: 60vh;
overflow-y: auto;
}
.synthetic-files-root .truncated-chip {
display: inline-flex;
align-items: center;
padding: 1px 8px;
border: 1px solid var(--amber, #f59e0b);
color: var(--amber, #f59e0b);
font-size: 0.6rem;
letter-spacing: 1.5px;
text-transform: uppercase;
}

View File

@@ -0,0 +1,376 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import api from '../../utils/api';
import { useEscapeKey } from '../../hooks/useEscapeKey';
import { useFocusTrap } from '../../hooks/useFocusTrap';
import { X } from '../../icons';
import { contentClassLabel, isCanaryClass } from '../../realism/labels';
// Reuse the DeckyFleet shell + the persona-page tweaks so this page reads
// the same as the rest of the realism nav group.
import '../DeckyFleet.css';
import '../PersonaGeneration.css';
import './SyntheticFiles.css';
// ─── Types ───────────────────────────────────────────────────────────────────
interface SyntheticFileRow {
uuid: string;
decky_uuid: string;
path: string;
persona: string;
content_class: string;
created_at: string;
last_modified: string;
edit_count: number;
content_hash: string;
}
interface SyntheticFileDetail extends SyntheticFileRow {
last_body: string;
truncated: boolean;
}
interface PaginatedResponse {
total: number;
limit: number;
offset: number;
data: SyntheticFileRow[];
}
interface DeckyOption {
uuid: string;
name: string;
}
const PAGE_SIZE = 50;
// Fixed list of content_class values mirroring decnet/realism/taxonomy.py.
// A static dropdown beats free-text — the operator sees what's actually
// available without a typo path failing silently.
const CONTENT_CLASSES = [
'note', 'todo', 'draft', 'script',
'log_cron', 'log_daemon', 'cache_tmp',
'canary_aws_creds', 'canary_env_file', 'canary_git_config',
'canary_ssh_key', 'canary_honeydoc', 'canary_honeydoc_docx',
'canary_honeydoc_pdf', 'canary_mysql_dump',
] as const;
// ─── Helpers ─────────────────────────────────────────────────────────────────
function fmt(iso: string): string {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
const pad = (n: number) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function deckyLabel(uuid: string, deckies: DeckyOption[]): string {
const d = deckies.find((d) => d.uuid === uuid);
return d ? d.name : `${uuid.slice(0, 8)}`;
}
// ─── Drawer ──────────────────────────────────────────────────────────────────
interface DrawerProps {
uuid: string;
deckies: DeckyOption[];
onClose: () => void;
}
const SyntheticFileDrawer: React.FC<DrawerProps> = ({ uuid, deckies, onClose }) => {
const panelRef = useRef<HTMLDivElement | null>(null);
useEscapeKey(onClose, true);
useFocusTrap(panelRef, true);
useEffect(() => {
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = prev; };
}, []);
const [row, setRow] = useState<SyntheticFileDetail | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
api.get<SyntheticFileDetail>(`/realism/synthetic-files/${encodeURIComponent(uuid)}`)
.then((res) => { if (!cancelled) setRow(res.data); })
.catch((err: any) => {
if (cancelled) return;
setError(err?.response?.status === 404 ? 'File no longer exists.' : 'Load failed.');
})
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [uuid]);
const canary = row ? isCanaryClass(row.content_class) : false;
return (
<div
className="drawer-backdrop"
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
>
<div ref={panelRef} role="dialog" aria-modal="true" className="drawer">
<div className="drawer-head">
<div>
<div className="drawer-eyebrow">
SYNTHETIC FILE{row ? ` · ${deckyLabel(row.decky_uuid, deckies)}` : ''}
</div>
<div className="drawer-title">{row?.path ?? uuid}</div>
</div>
<button onClick={onClose} aria-label="Close" className="drawer-close">
<X size={18} />
</button>
</div>
{loading && <div className="dim">Loading</div>}
{error && <div className="alert-text">{error}</div>}
{row && (
<>
<div className="meta-grid">
<div className="label">Persona</div>
<div>{row.persona}</div>
<div className="label">Content Class</div>
<div>
<span className={canary ? 'value-canary' : ''}>
{contentClassLabel(row.content_class)}
</span>
<span className="mono dim" style={{ marginLeft: 8, fontSize: '0.75rem' }}>
{row.content_class}
</span>
</div>
<div className="label">Edit Count</div>
<div className="mono">{row.edit_count}</div>
<div className="label">Created</div>
<div className="mono dim">{fmt(row.created_at)}</div>
<div className="label">Last Modified</div>
<div className="mono">{fmt(row.last_modified)}</div>
<div className="label">Content Hash</div>
<div className="mono dim" style={{ wordBreak: 'break-all' }}>
{row.content_hash}
</div>
</div>
<div className="body-head">
<span>BODY PREVIEW · {(row.last_body?.length ?? 0).toLocaleString()} BYTES</span>
{row.truncated && (
<span
className="truncated-chip"
title="Body is at the 64KB cap; the decky filesystem holds the canonical bytes."
>
TRUNCATED
</span>
)}
</div>
<pre className="body-pre">
{row.last_body || <span className="dim"></span>}
</pre>
</>
)}
</div>
</div>
);
};
// ─── Page ────────────────────────────────────────────────────────────────────
const SyntheticFiles: React.FC = () => {
const [rows, setRows] = useState<SyntheticFileRow[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deckies, setDeckies] = useState<DeckyOption[]>([]);
const [deckyFilter, setDeckyFilter] = useState<string>(''); // '' = all
const [personaFilter, setPersonaFilter] = useState<string>('');
const [classFilter, setClassFilter] = useState<string>('');
const [selectedUuid, setSelectedUuid] = useState<string | null>(null);
useEffect(() => {
api.get<DeckyOption[]>('/deckies')
.then((res) => setDeckies(Array.isArray(res.data) ? res.data : []))
.catch(() => setDeckies([]));
}, []);
const personaOptions = useMemo(() => {
const set = new Set<string>();
rows.forEach((r) => set.add(r.persona));
return Array.from(set).sort();
}, [rows]);
const fetchRows = async () => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams();
params.set('limit', String(PAGE_SIZE));
params.set('offset', String(page * PAGE_SIZE));
if (deckyFilter) params.set('decky_uuid', deckyFilter);
if (personaFilter) params.set('persona', personaFilter);
if (classFilter) params.set('content_class', classFilter);
const res = await api.get<PaginatedResponse>(
`/realism/synthetic-files?${params.toString()}`,
);
setRows(res.data.data);
setTotal(res.data.total);
} catch (err: any) {
setError(err?.response?.status === 401 ? 'Authentication required.' : 'Load failed.');
} finally {
setLoading(false);
}
};
useEffect(() => { fetchRows(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [
page, deckyFilter, personaFilter, classFilter,
]);
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
const filtersActive = !!(deckyFilter || personaFilter || classFilter);
return (
<div className="fleet-root synthetic-files-root">
<div className="page-header">
<div className="page-title-group">
<h1>SYNTHETIC FILES</h1>
<span className="page-sub">
{total} TOTAL · PAGE {page + 1} / {totalPages}
{filtersActive ? ' · FILTERED' : ''}
</span>
</div>
<div className="actions filters">
<div className="filter-group">
<label>Decky</label>
<select
className="filter-input"
value={deckyFilter}
onChange={(e) => { setDeckyFilter(e.target.value); setPage(0); }}
>
<option value="">All</option>
{deckies.map((d) => (
<option key={d.uuid} value={d.uuid}>{d.name}</option>
))}
</select>
</div>
<div className="filter-group">
<label>Persona</label>
<select
className="filter-input"
value={personaFilter}
onChange={(e) => { setPersonaFilter(e.target.value); setPage(0); }}
>
<option value="">All</option>
{personaOptions.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
<div className="filter-group">
<label>Content Class</label>
<select
className="filter-input"
value={classFilter}
onChange={(e) => { setClassFilter(e.target.value); setPage(0); }}
>
<option value="">All</option>
{CONTENT_CLASSES.map((c) => (
<option key={c} value={c}>{contentClassLabel(c)}</option>
))}
</select>
</div>
</div>
</div>
<div className="info-banner">
<div>
<strong>Scope:</strong> read-only inventory of files the realism
worker has grown across the fleet. The orchestrator is the sole
writer; rows persist in the{' '}
<span className="mono matrix-text">synthetic_files</span> table.
Click any row for the body preview and lineage detail.
</div>
{error && (
<div className="info-line alert-text" style={{ marginTop: 8 }}>{error}</div>
)}
</div>
<div className="files-table-wrap">
<table className="files-table">
<thead>
<tr>
<th>Decky</th>
<th>Path</th>
<th>Persona</th>
<th>Class</th>
<th>Last Modified</th>
<th>Edits</th>
<th>Hash</th>
</tr>
</thead>
<tbody>
{loading && (
<tr className="empty-row"><td colSpan={7}>Loading</td></tr>
)}
{!loading && rows.length === 0 && (
<tr className="empty-row"><td colSpan={7}>
No files match the current filters.
</td></tr>
)}
{!loading && rows.map((r) => {
const canary = isCanaryClass(r.content_class);
return (
<tr key={r.uuid} onClick={() => setSelectedUuid(r.uuid)}>
<td>{deckyLabel(r.decky_uuid, deckies)}</td>
<td className="path">{r.path}</td>
<td>{r.persona}</td>
<td className={`cls${canary ? ' canary' : ''}`}>
{contentClassLabel(r.content_class)}
</td>
<td className="dim-time">{fmt(r.last_modified)}</td>
<td className="mono">{r.edit_count}</td>
<td className="hash">{r.content_hash.slice(0, 12)}</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="pager">
<button
className="btn ghost small"
onClick={() => setPage((p) => Math.max(0, p - 1))}
disabled={page === 0}
>
PREV
</button>
<span className="page-counter">PAGE {page + 1} / {totalPages}</span>
<button
className="btn ghost small"
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={page >= totalPages - 1}
>
NEXT
</button>
</div>
{selectedUuid && (
<SyntheticFileDrawer
uuid={selectedUuid}
deckies={deckies}
onClose={() => setSelectedUuid(null)}
/>
)}
</div>
);
};
export default SyntheticFiles;