From 1de4136ed97f6dee3e732bd9306ff9cf806fe3fd Mon Sep 17 00:00:00 2001 From: anti Date: Mon, 27 Apr 2026 18:08:58 -0400 Subject: [PATCH] style(realism-ui): adopt the persona-page design language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both pages now layer on DeckyFleet.css + PersonaGeneration.css and use the project's house vocabulary — fleet-root shell, page-header with title-group + actions, btn / btn.violet / btn.ghost, info-banner with the violet left rule, and the dim/matrix/alert text accents. RealismConfig: inputs are flush-styled weight-input fields with a violet focus ring; section heads carry a TOTAL badge; canary rows get the project's amber accent; canary probability lives in a panel-bordered slider row. SyntheticFiles: the inline-styled table is now a styled .files-table with the standard hover affordance, the filter-row uses tweak-group label+select pairs, the drawer carries .drawer-eyebrow / .drawer-title / .meta-grid in the same style as the canary token drawer, and pager buttons share the .btn.ghost.small treatment. No behavioural change. --- .../RealismConfig/RealismConfig.css | 122 +++++++ .../RealismConfig/RealismConfig.tsx | 260 +++++++-------- .../SyntheticFiles/SyntheticFiles.css | 214 ++++++++++++ .../SyntheticFiles/SyntheticFiles.tsx | 306 +++++++++--------- 4 files changed, 624 insertions(+), 278 deletions(-) create mode 100644 decnet_web/src/components/RealismConfig/RealismConfig.css create mode 100644 decnet_web/src/components/SyntheticFiles/SyntheticFiles.css diff --git a/decnet_web/src/components/RealismConfig/RealismConfig.css b/decnet_web/src/components/RealismConfig/RealismConfig.css new file mode 100644 index 00000000..5a936e41 --- /dev/null +++ b/decnet_web/src/components/RealismConfig/RealismConfig.css @@ -0,0 +1,122 @@ +/* Realism Config — layered on DeckyFleet.css. + Adds: weight tables (label + numeric input + percent share), + canary-class accent, canary-probability slider row. */ + +.realism-config-root .mono { font-family: var(--font-mono); } + +.realism-config-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; +} +.realism-config-root .info-banner em { color: var(--matrix); font-style: normal; } + +/* Section heading above each weight table. */ +.realism-config-root .section-head { + display: flex; + justify-content: space-between; + align-items: baseline; + font-size: 0.7rem; + letter-spacing: 1.5px; + color: var(--dim); + text-transform: uppercase; + margin: 0 0 8px; +} +.realism-config-root .section-head .total { + font-family: var(--font-mono); + color: var(--matrix); + letter-spacing: 1px; +} +.realism-config-root .section-help { + font-size: 0.7rem; + opacity: 0.45; + letter-spacing: 0.5px; + margin: -4px 0 8px; +} + +/* Weight table: class label · raw enum · weight input · percent share. */ +.realism-config-root .weight-table { + width: 100%; + border-collapse: collapse; + background: var(--panel); + border: 1px solid var(--border); + margin-bottom: 24px; + font-size: 0.82rem; +} +.realism-config-root .weight-table tr { + border-bottom: 1px solid var(--border); +} +.realism-config-root .weight-table tr:last-child { border-bottom: none; } +.realism-config-root .weight-table td { + padding: 8px 14px; + vertical-align: middle; +} +.realism-config-root .weight-table td.cls { width: 50%; } +.realism-config-root .weight-table td.cls .cls-label { font-weight: 600; } +.realism-config-root .weight-table td.cls .cls-enum { + margin-left: 10px; + font-family: var(--font-mono); + font-size: 0.7rem; + color: var(--dim); + letter-spacing: 0.5px; +} +.realism-config-root .weight-table td.cls.canary .cls-label { + color: var(--amber, #f59e0b); +} +.realism-config-root .weight-table td.weight { + width: 130px; +} +.realism-config-root .weight-table input.weight-input { + width: 90px; + background: var(--bg-elev, rgba(0, 0, 0, 0.3)); + border: 1px solid var(--border); + color: var(--matrix); + padding: 5px 9px; + font-family: var(--font-mono); + font-size: 0.85rem; + outline: none; + transition: border-color 0.15s, box-shadow 0.15s; + text-align: right; +} +.realism-config-root .weight-table input.weight-input:focus { + border-color: var(--violet); + box-shadow: 0 0 0 1px var(--violet); +} +.realism-config-root .weight-table td.share { + font-family: var(--font-mono); + color: var(--dim); + width: 80px; + text-align: right; +} + +/* Canary probability slider row. */ +.realism-config-root .prob-row { + display: flex; + align-items: center; + gap: 16px; + background: var(--panel); + border: 1px solid var(--border); + padding: 12px 16px; + margin-bottom: 24px; +} +.realism-config-root .prob-row input[type="range"] { + flex: 1; + accent-color: var(--violet); +} +.realism-config-root .prob-row .prob-value { + font-family: var(--font-mono); + font-size: 1rem; + color: var(--matrix); + letter-spacing: 1px; + min-width: 70px; + text-align: right; +} + +.realism-config-root .footer-actions { + display: flex; + gap: 12px; + margin-top: 8px; +} diff --git a/decnet_web/src/components/RealismConfig/RealismConfig.tsx b/decnet_web/src/components/RealismConfig/RealismConfig.tsx index 69834e3c..9f5eb179 100644 --- a/decnet_web/src/components/RealismConfig/RealismConfig.tsx +++ b/decnet_web/src/components/RealismConfig/RealismConfig.tsx @@ -1,8 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import api from '../../utils/api'; import { useToast } from '../Toasts/useToast'; -import { Sliders, Save, RotateCcw } from '../../icons'; +import { Save, RotateCcw, AlertTriangle } from '../../icons'; import { contentClassLabel, isCanaryClass } from '../../realism/labels'; +// Reuse the DeckyFleet shell (page-header / btn / fleet-* / dim / mono) and +// the persona-page tweaks (info-banner, .input) so the realism config panel +// reads the same as the rest of the realism nav group. +import '../DeckyFleet.css'; +import '../PersonaGeneration.css'; +import './RealismConfig.css'; // ─── Types ─────────────────────────────────────────────────────────────────── @@ -43,73 +49,61 @@ const DEFAULTS: ConfigPayload = { canary_probability: 0.03, }; -// ─── Helpers ───────────────────────────────────────────────────────────────── - -function pct(weights: WeightEntry[], idx: number): string { - const total = weights.reduce((s, w) => s + Math.max(0, w.weight), 0); - if (total === 0) return '—'; - return `${((weights[idx].weight / total) * 100).toFixed(1)}%`; -} - // ─── Subcomponent ──────────────────────────────────────────────────────────── const WeightTable: React.FC<{ title: string; + help: string; weights: WeightEntry[]; onChange: (next: WeightEntry[]) => void; -}> = ({ title, weights, onChange }) => { +}> = ({ title, help, weights, onChange }) => { const total = weights.reduce((s, w) => s + Math.max(0, w.weight), 0); return ( -
-
- {title} · TOTAL {total} + <> +
+ {title} + TOTAL {total}
- +
{help}
+
- {weights.map((w, i) => ( - - - - - - ))} + {weights.map((w, i) => { + const canary = isCanaryClass(w.content_class); + const share = + total === 0 + ? '—' + : `${((Math.max(0, w.weight) / total) * 100).toFixed(1)}%`; + return ( + + + + + + ); + })}
- - {contentClassLabel(w.content_class)} - - - {w.content_class} - - - { - const next = weights.slice(); - const v = parseInt(e.target.value, 10); - next[i] = { ...next[i], weight: Number.isFinite(v) ? Math.max(0, v) : 0 }; - onChange(next); - }} - style={{ - width: '80px', - backgroundColor: 'rgba(255,255,255,0.03)', - color: 'var(--text-color)', - border: '1px solid rgba(255,255,255,0.1)', - padding: '4px 8px', fontFamily: 'inherit', - }} - /> - - {pct(weights, i)} -
+ {contentClassLabel(w.content_class)} + {w.content_class} + + { + const v = parseInt(e.target.value, 10); + const next = weights.slice(); + next[i] = { + ...next[i], + weight: Number.isFinite(v) ? Math.max(0, v) : 0, + }; + onChange(next); + }} + /> + {share}
-
+ ); }; @@ -156,98 +150,110 @@ const RealismConfig: React.FC = () => { }; const handleReset = () => { - if (!window.confirm('Reset to baked-in defaults? This will overwrite the current saved config on next save.')) return; + if (!window.confirm( + 'Reset to baked-in defaults? This will overwrite the saved config on next save.', + )) return; setConfig(DEFAULTS); }; - return ( -
-
- -

REALISM CONFIG

-
-

- Operator-tuned planner weights. The orchestrator refreshes from the DB - every ~5 minutes; saved changes land within one refresh window. -

+ const totals = useMemo(() => ({ + user: config.user_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0), + system: config.system_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0), + canary: config.canary_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0), + }), [config]); - {error &&
{error}
} + return ( +
+
+
+

REALISM CONFIG

+ + USER {totals.user} · SYSTEM {totals.system} · CANARY {totals.canary} · + {' '}CANARY PROB {(config.canary_probability * 100).toFixed(1)}% + +
+
+ + +
+
+ +
+
+ Scope: tunes the orchestrator's realism planner + {' '}— how often each kind of synthetic file lands on a decky, and + how rare canary plants are. Persisted in the{' '} + realism_config table; the + orchestrator refreshes from the DB every ~5 minutes. +
+ {error && ( +
+ {error} +
+ )} +
{loading ? ( -
Loading…
+
Loading…
) : ( <> setConfig({ ...config, user_class_weights: next })} /> setConfig({ ...config, system_class_weights: next })} /> setConfig({ ...config, canary_class_weights: next })} /> -
-
- CANARY PROBABILITY · share of file picks that materialise a canary -
-
- setConfig({ - ...config, - canary_probability: parseFloat(e.target.value), - })} - style={{ flex: 1 }} - /> - - {(config.canary_probability * 100).toFixed(1)}% - -
+
+ Canary Probability + {(config.canary_probability * 100).toFixed(1)}%
- -
- - +
+ Share of file picks that materialise a canary. Each plant + creates a real canary token row + DNS slug or HTTP URL — + keeping this rare prevents a noisy alert surface. +
+
+ setConfig({ + ...config, + canary_probability: parseFloat(e.target.value), + })} + /> + + {(config.canary_probability * 100).toFixed(1)}% +
)} diff --git a/decnet_web/src/components/SyntheticFiles/SyntheticFiles.css b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.css new file mode 100644 index 00000000..0fa10467 --- /dev/null +++ b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.css @@ -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; +} diff --git a/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx index 005cc29f..a327adef 100644 --- a/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx +++ b/decnet_web/src/components/SyntheticFiles/SyntheticFiles.tsx @@ -2,8 +2,13 @@ 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, FileText } from '../../icons'; +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 ─────────────────────────────────────────────────────────────────── @@ -43,8 +48,7 @@ const PAGE_SIZE = 50; // available without a typo path failing silently. const CONTENT_CLASSES = [ 'note', 'todo', 'draft', 'script', - 'log_cron', 'log_daemon', - 'cache_tmp', 'config_local', + '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', @@ -100,89 +104,74 @@ const SyntheticFileDrawer: React.FC = ({ uuid, deckies, onClose }) return () => { cancelled = true; }; }, [uuid]); + const canary = row ? isCanaryClass(row.content_class) : false; + return (
{ if (e.target === e.currentTarget) onClose(); }} - style={{ - position: 'fixed', inset: 0, - backgroundColor: 'rgba(0,0,0,0.6)', - display: 'flex', justifyContent: 'flex-end', - zIndex: 1000, - }} > -
-
+
+
-
- SYNTHETIC FILE {row ? `· ${deckyLabel(row.decky_uuid, deckies)}` : ''} -
-
- {row?.path ?? uuid} +
+ SYNTHETIC FILE{row ? ` · ${deckyLabel(row.decky_uuid, deckies)}` : ''}
+
{row?.path ?? uuid}
-
- {loading &&
Loading…
} - {error &&
{error}
} + {loading &&
Loading…
} + {error &&
{error}
} {row && ( <> -
-
PERSONA
{row.persona}
-
CONTENT CLASS
+
+
Persona
+
{row.persona}
+ +
Content Class
- + {contentClassLabel(row.content_class)} - + {row.content_class}
-
EDIT COUNT
{row.edit_count}
-
CREATED
{fmt(row.created_at)}
-
LAST MODIFIED
{fmt(row.last_modified)}
-
CONTENT HASH
-
{row.content_hash}
+ +
Edit Count
+
{row.edit_count}
+ +
Created
+
{fmt(row.created_at)}
+ +
Last Modified
+
{fmt(row.last_modified)}
+ +
Content Hash
+
+ {row.content_hash} +
-
-
- - BODY PREVIEW ({(row.last_body?.length ?? 0).toLocaleString()} bytes) +
+ BODY PREVIEW · {(row.last_body?.length ?? 0).toLocaleString()} BYTES + {row.truncated && ( + + TRUNCATED - {row.truncated && ( - - TRUNCATED - - )} -
-
-                {row.last_body || }
-              
+ )}
+
+              {row.last_body || }
+            
)}
@@ -245,116 +234,131 @@ const SyntheticFiles: React.FC = () => { ]); const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + const filtersActive = !!(deckyFilter || personaFilter || classFilter); return ( -
-
- -

SYNTHETIC FILES

- - {total} total - +
+
+
+

SYNTHETIC FILES

+ + {total} TOTAL · PAGE {page + 1} / {totalPages} + {filtersActive ? ' · FILTERED' : ''} + +
+
+
+ + +
+
+ + +
+
+ + +
+
-
- - - - - +
+
+ Scope: read-only inventory of files the realism + worker has grown across the fleet. The orchestrator is the sole + writer; rows persist in the{' '} + synthetic_files table. + Click any row for the body preview and lineage detail. +
+ {error && ( +
{error}
+ )}
- {error &&
{error}
} - -
- +
+
- - - - - - - - + + + + + + + + {loading && ( - + )} {!loading && rows.length === 0 && ( - )} - {!loading && rows.map((r) => ( - setSelectedUuid(r.uuid)} - style={{ cursor: 'pointer', borderBottom: '1px solid rgba(255,255,255,0.03)' }} - > - - - - - - - - - ))} + {!loading && rows.map((r) => { + const canary = isCanaryClass(r.content_class); + return ( + setSelectedUuid(r.uuid)}> + + + + + + + + + ); + })}
DECKYPATHPERSONACLASSLAST MODIFIEDEDITSHASH
DeckyPathPersonaClassLast ModifiedEditsHash
Loading…
Loading…
+
No files match the current filters.
{deckyLabel(r.decky_uuid, deckies)}{r.path}{r.persona} - {contentClassLabel(r.content_class)} - {fmt(r.last_modified)}{r.edit_count}{r.content_hash.slice(0, 12)}…
{deckyLabel(r.decky_uuid, deckies)}{r.path}{r.persona} + {contentClassLabel(r.content_class)} + {fmt(r.last_modified)}{r.edit_count}{r.content_hash.slice(0, 12)}…
-
- - - Page {page + 1} / {totalPages} - -