From ba0e7ca476cf33276a5b45eef22ac85dd44c4e92 Mon Sep 17 00:00:00 2001 From: anti Date: Wed, 29 Apr 2026 11:50:35 -0400 Subject: [PATCH] style(ui): rebuild ServiceConfigForm in inspector terminal vocabulary Previous CSS lived in DeckyFleet.css only, so when the form rendered inside MazeNET Inspector the inputs fell back to browser defaults (white-on-white, oversized labels, mismatched buttons). New ServiceConfigForm.css ships with the component itself: small uppercase tracking-1 labels at 0.6rem (matches kvs .k), dark transparent inputs with violet focus, matrix-green text inside inputs, custom select chevron, dedicated svc-cfg-btn that visually mirrors maze-btn.small, password reveal toggle, and a 96px label column so labels never wrap into the input. Help text drops to 0.58rem dim under the input. Works identically in both surfaces. --- decnet_web/src/components/DeckyFleet.css | 55 ------ .../src/components/ServiceConfigForm.css | 183 ++++++++++++++++++ .../src/components/ServiceConfigForm.tsx | 15 +- 3 files changed, 191 insertions(+), 62 deletions(-) create mode 100644 decnet_web/src/components/ServiceConfigForm.css diff --git a/decnet_web/src/components/DeckyFleet.css b/decnet_web/src/components/DeckyFleet.css index 2a839453..374dd0b6 100644 --- a/decnet_web/src/components/DeckyFleet.css +++ b/decnet_web/src/components/DeckyFleet.css @@ -127,61 +127,6 @@ } .decky-hits { font-variant-numeric: tabular-nums; } -/* Schema-driven per-service config form (shared with MazeNET Inspector). */ -.service-config-form { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 8px; - padding: 10px; - border: 1px dashed var(--border); - border-radius: 2px; -} -.svc-cfg-row { display: flex; flex-direction: column; gap: 4px; } -.svc-cfg-label { - font-size: 0.62rem; - letter-spacing: 1px; - text-transform: uppercase; - opacity: 0.7; -} -.svc-cfg-secret-tag { font-size: 0.55rem; opacity: 0.6; letter-spacing: 1px; } -.svc-cfg-input { - flex: 1; - font-size: 0.72rem; - padding: 4px 6px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid var(--border); - color: inherit; - font-family: inherit; -} -.svc-cfg-input:focus { outline: 1px solid var(--violet); } -.svc-cfg-pw-wrap { display: flex; gap: 6px; align-items: stretch; } -.svc-cfg-help { font-size: 0.62rem; opacity: 0.55; } -.svc-cfg-actions { - display: flex; - gap: 6px; - align-items: center; - justify-content: flex-end; - margin-top: 4px; -} -.svc-cfg-dirty-tag { - font-size: 0.6rem; - letter-spacing: 1px; - color: var(--violet); - margin-right: auto; -} -.svc-cfg-toggle-btn { - background: transparent; - border: none; - color: inherit; - cursor: pointer; - font-size: 0.62rem; - letter-spacing: 1px; - opacity: 0.7; - padding: 0; -} -.svc-cfg-toggle-btn:hover { opacity: 1; } - /* Status dots */ .status-dot { display: inline-block; diff --git a/decnet_web/src/components/ServiceConfigForm.css b/decnet_web/src/components/ServiceConfigForm.css new file mode 100644 index 00000000..4228b2db --- /dev/null +++ b/decnet_web/src/components/ServiceConfigForm.css @@ -0,0 +1,183 @@ +/* Schema-driven per-service config form. + * + * Used inside both the Fleet DeckyCard and the MazeNET Inspector — the + * styles intentionally don't depend on either page's root class so the + * form looks identical in both surfaces. Vocabulary mirrors the + * existing INSPECTOR kvs grid (small uppercase labels, dark transparent + * inputs, matrix/violet accents). + */ + +.service-config-form { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 10px; + padding-top: 10px; + border-top: 1px dashed var(--border); + font-family: var(--font-mono); +} + +.svc-cfg-row { + display: grid; + grid-template-columns: 96px 1fr; + gap: 4px 10px; + align-items: center; +} + +.svc-cfg-label { + grid-column: 1 / 2; + grid-row: 1 / 2; + font-size: 0.6rem; + letter-spacing: 1px; + text-transform: uppercase; + opacity: 0.55; + align-self: center; +} + +.svc-cfg-secret-tag { + font-size: 0.52rem; + opacity: 0.5; + letter-spacing: 1px; + margin-left: 4px; +} + +.svc-cfg-input, +textarea.svc-cfg-input, +select.svc-cfg-input { + grid-column: 2 / 3; + grid-row: 1 / 2; + width: 100%; + box-sizing: border-box; + font-family: var(--font-mono); + font-size: 0.72rem; + padding: 4px 6px; + background: rgba(0, 0, 0, 0.35); + border: 1px solid var(--border); + border-radius: 0; + color: var(--matrix); + outline: none; + transition: border-color 80ms linear; +} + +.svc-cfg-input:focus, +textarea.svc-cfg-input:focus, +select.svc-cfg-input:focus { + border-color: var(--violet); +} + +textarea.svc-cfg-input { + min-height: 48px; + resize: vertical; + line-height: 1.3; +} + +select.svc-cfg-input { + appearance: none; + -webkit-appearance: none; + padding-right: 18px; + background-image: + linear-gradient(45deg, transparent 50%, var(--matrix) 50%), + linear-gradient(135deg, var(--matrix) 50%, transparent 50%); + background-position: + calc(100% - 10px) 50%, + calc(100% - 6px) 50%; + background-size: 4px 4px, 4px 4px; + background-repeat: no-repeat; +} + +/* Checkbox sits left-aligned in the input column. */ +.svc-cfg-row input[type="checkbox"] { + grid-column: 2 / 3; + grid-row: 1 / 2; + justify-self: start; + width: 14px; + height: 14px; + accent-color: var(--violet); +} + +/* Password row: input + reveal button share the input column. */ +.svc-cfg-pw-wrap { + grid-column: 2 / 3; + grid-row: 1 / 2; + display: flex; + gap: 6px; + align-items: stretch; +} +.svc-cfg-pw-wrap .svc-cfg-input { grid-column: auto; grid-row: auto; flex: 1; } + +/* Help text: full-width row under the input. */ +.svc-cfg-help { + grid-column: 1 / 3; + grid-row: 2 / 3; + font-size: 0.58rem; + opacity: 0.45; + line-height: 1.35; + margin-top: 2px; +} + +/* Action row matches .maze-btn.small visual weight. */ +.svc-cfg-actions { + display: flex; + gap: 6px; + align-items: center; + justify-content: flex-end; + margin-top: 6px; +} + +.svc-cfg-dirty-tag { + font-size: 0.58rem; + letter-spacing: 1px; + color: var(--violet); + margin-right: auto; + opacity: 0.85; +} + +.svc-cfg-btn { + font-family: var(--font-mono); + font-size: 0.62rem; + letter-spacing: 1px; + text-transform: uppercase; + padding: 4px 10px; + background: transparent; + border: 1px solid var(--matrix); + color: var(--matrix); + cursor: pointer; + transition: background 80ms linear, color 80ms linear, box-shadow 80ms linear; +} +.svc-cfg-btn:hover:not(:disabled) { + background: var(--matrix); + color: #000; +} +.svc-cfg-btn.violet { border-color: var(--violet); color: var(--violet); } +.svc-cfg-btn.violet:hover:not(:disabled) { background: var(--violet); color: #000; } +.svc-cfg-btn:disabled { opacity: 0.3; cursor: not-allowed; } + +.svc-cfg-pw-toggle { + font-family: var(--font-mono); + font-size: 0.55rem; + letter-spacing: 1px; + padding: 0 6px; + background: transparent; + border: 1px solid var(--border); + color: inherit; + cursor: pointer; + opacity: 0.7; +} +.svc-cfg-pw-toggle:hover { opacity: 1; border-color: var(--violet); } + +.svc-cfg-toggle-btn { + background: transparent; + border: none; + color: inherit; + cursor: pointer; + padding: 0; + font: inherit; + letter-spacing: inherit; +} + +.svc-cfg-status { + font-size: 0.62rem; + opacity: 0.7; + font-style: italic; +} +.svc-cfg-status.alert-text { font-style: normal; } diff --git a/decnet_web/src/components/ServiceConfigForm.tsx b/decnet_web/src/components/ServiceConfigForm.tsx index a29986a9..c1336162 100644 --- a/decnet_web/src/components/ServiceConfigForm.tsx +++ b/decnet_web/src/components/ServiceConfigForm.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import api from '../utils/api'; import { useToast } from './Toasts/useToast'; +import './ServiceConfigForm.css'; export interface ServiceConfigFieldDTO { key: string; @@ -170,14 +171,14 @@ const ServiceConfigForm: React.FC = ({ }; if (loadErr) { - return
{loadErr}
; + return
{loadErr}
; } if (!schema) { - return
Loading schema…
; + return
Loading schema…
; } if (schema.fields.length === 0) { return ( -
+
No customizable fields for {schema.name}.
); @@ -236,7 +237,7 @@ const ServiceConfigForm: React.FC = ({ />