diff --git a/docs/workflow-designer-features.md b/docs/workflow-designer-features.md index 845c2c2..da9b7f6 100644 --- a/docs/workflow-designer-features.md +++ b/docs/workflow-designer-features.md @@ -43,7 +43,7 @@ Vollständige Auflistung sämtlicher Features des NodePilot-Workflow-Designers ( - **Controls-Panel:** Standard React-Flow-Controls (Zoom in/out, Fit, Lock). - **Hintergrund:** Ein **Punkt-Raster** (Gap 24 px, Dot-Größe 1.6) — identisch in Premium **und** Classic, der frühere zweistufige Crosshatch ist ersatzlos entfallen. Die Deckkraft ist skin-abhängig: helle Skins tragen einen deutlich stärkeren Alpha, weil das Raster auf hellem Grund mehr Kontrast braucht als auf dunklem. Bei aktivem Snap-to-Grid ersetzt ein **Linien**-Raster auf der Snap-Schrittweite das Punkt-Raster. - **Snap-to-Grid (Expert):** `G` togglet Raster-Snapping (Standard 20 px, konfigurierbar). -- **Node-Skalierung:** 8 Größen-Presets (XS … 4XL, Default SM); `Ctrl+Shift+>` / `Ctrl+Shift+<` (Expert) ändern Node-Größe, `Ctrl+Alt+.` / `Ctrl+Alt+,` die Label-Schriftgröße (nur Classic-Node-Stil). +- **Node-Skalierung:** 8 Größen-Presets (XS … 4XL, Default L); `Ctrl+Shift+>` / `Ctrl+Shift+<` (Expert) ändern Node-Größe, `Ctrl+Alt+.` / `Ctrl+Alt+,` die Label-Schriftgröße (nur Classic-Node-Stil). - **Fullscreen / Distraction-Free:** `F11` blendet Sidebar, Panels und Banner aus, behält Header + Canvas + Exit-Pill. - **Viewport-Virtualisierung:** Nur sichtbare Elemente werden gerendert (`onlyRenderVisibleElements`) — spürbar ab ~50 Nodes. @@ -337,7 +337,7 @@ Alle Darstellungs-Einstellungen liegen im **`designStore`** (Zustand + persist, | `designerTheme` | `atelier` | atelier / classic | Designsprache des Designers: **Atelier** (eigener Werkbank-Look — Papier/Graphit-Grund mit dem gewohnten Karo-Raster, schwebendes Karten-Chrome, ein Akzent; `styles/designer-atelier.css` mappt die `--color-*`-Tokens über eine `--wd-*`-Palettenschicht) vs. **Classic** (bisheriger Look, byte-identisch). **Farb-Skins adaptieren beide Looks:** im Atelier re-pointet jeder Skin Akzentfamilie + Grundton (`--wd-accent*`, `--wd-canvas`/`--wd-panel`), die Atelier-Geometrie bleibt universell; Status-Farben bleiben in beiden Looks skin-stabil. Umschalter (`Brush`-Icon, `role="switch"`, `data-testid="toggle-atelier-theme"`) in beiden Kopfleisten-Layouts | | `toolbarLayout` | `compact` | compact / classic | Kopfleisten-Layout: kompakt (gruppierte Popover-Menüs, grüner „Ausführen") vs. klassische Inline-Reihe (jeder Toggle/Tool als einzelner Button, icon-only Play). Umschalter-Button in beiden Layouts | | `nodeStyle` | `classic` | classic / card | Node-Darstellung (`Ctrl+Shift+N`) | -| `nodeScaleIndex` | 1 | 0–7 (XS … 4XL) | Node-Größe | +| `nodeScaleIndex` | 3 | 0–7 (XS … 4XL) | Node-Größe | | `labelFontOffsetIndex` | 2 | 0–6 (−4 … +8 px) | Label-Schriftgröße | | `edgesAnimated` | true | bool | Fluss-Animation der Kanten (`A`) | | `edgeWidthIndex` | 2 | 1.5/2/2.5/3.5/5/7 px | Kantenbreite (`Ctrl+]`/`[`) | diff --git a/src/nodepilot-ui/CLAUDE.md b/src/nodepilot-ui/CLAUDE.md index b384e6a..3c6833c 100644 --- a/src/nodepilot-ui/CLAUDE.md +++ b/src/nodepilot-ui/CLAUDE.md @@ -14,7 +14,11 @@ Gilt für `src/nodepilot-ui/`. Projektweite Regeln stehen in der Root-`CLAUDE.md - Client-State via Zustand-Stores (`src/stores/`), Server-State via TanStack React Query (`refetchOnWindowFocus:false`, SignalR invalidiert Caches). - **Typografie:** `IBM Plex Sans Variable` (`--font-headline`/`--font-body`/`--font-label`, alle drei identisch) + `IBM Plex Mono` (`--font-mono`), deklariert im `@theme` von `index.css`. Beide self-hosted über fontsource — **keine externen Font-Requests**: die Prod-CSP kennt kein `font-src` und fällt auf `default-src 'self'`, ein CDN-Font wäre in Produktion geblockt. Body trägt ein `font-size-adjust` als einzigen Dichte-Ausgleich (Plex hat eine kleinere x-Höhe als das früher genutzte Inter); Monospace ist davon ausgenommen. Monaco kann keine CSS-Variable verwerten und hält den Stack als `MONO_FONT_STACK` in `lib/monacoSetup.ts` — `fontTokens.test.ts` hält beide Seiten deckungsgleich. Die Doku-Website fährt bewusst ein eigenes Type-System (Geist + JetBrains Mono). -- **Formularfelder:** `.input-field` (index.css) statt handgebauter `border …`-Klassenketten. Der Stil ist *versenkt* (dunkler als der Container + Inset-Schatten) und setzt damit eine **angehobene** Fläche voraus — `.np-card` bzw. das `ModalShell`-Panel (`.np-modal-panel`, dark = `surface-container`). Wer einen eigenen Dialog-Container baut, muss diese Anhebung mitnehmen, sonst malt sich das Feld in der Farbe seines Containers und verschwindet. +- **Formularfelder — zwei Ebenen, nicht eine.** Welche gilt, entscheidet die Fläche darunter: + - **`.input-field`** (index.css) für Felder auf einer angehobenen Fläche: Designer-Property-Panels, `.np-card`. Der Stil ist *versenkt* (dunkler als der Container + Inset-Schatten) und **setzt diese Anhebung voraus**. Handgebaute `border …`-Ketten sind dort falsch. + - **Umriss-Kette** `px-3 py-2 border border-outline-variant rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500` (+ `w-full`, bei ` setForm({ ...form, name: e.target.value })} className="input-field" /> + setForm({ ...form, name: e.target.value })} className={`${FIELD} w-full`} /> setForm({ ...form, key: e.target.value })} - placeholder="disk-check" pattern="[A-Za-z0-9_\-]+" className="input-field font-mono disabled:opacity-60" /> + placeholder="disk-check" pattern="[A-Za-z0-9_\-]+" className={`${FIELD} w-full font-mono disabled:opacity-60`} />
- - setForm({ ...form, description: e.target.value })} className="input-field" /> + setForm({ ...form, description: e.target.value })} className={`${FIELD} w-full`} /> setForm({ ...form, color: e.target.value })} className="h-9 w-12 rounded border border-outline-variant" /> @@ -378,26 +411,28 @@ export function CustomActivitiesPage() {
- setForm({ ...form, engine: e.target.value })} className={`${FIELD} w-full bg-surface-lowest`}> - setForm({ ...form, defaultTimeoutSeconds: e.target.value })} className="input-field" /> + setForm({ ...form, defaultTimeoutSeconds: e.target.value })} className={`${FIELD} w-full`} />
- setForm({ ...form, successExitCodes: e.target.value })} placeholder="0,1" className="input-field w-28 font-mono" /> + setForm({ ...form, successExitCodes: e.target.value })} placeholder="0,1" className={`${FIELD} w-28 font-mono`} />
@@ -443,7 +478,7 @@ export function CustomActivitiesPage() {
setIconSearch(e.target.value)} placeholder={t('customActivities:iconPicker.search')} - className="input-field mb-3 font-mono" /> + className={`${FIELD} w-full mb-3 font-mono`} />
{CUSTOM_ACTIVITY_ICON_CHOICES.filter((ic) => ic.includes(iconSearch.trim().toLowerCase())).map((ic) => (
diff --git a/src/nodepilot-ui/src/pages/MobileWorkflowView.tsx b/src/nodepilot-ui/src/pages/MobileWorkflowView.tsx index bb92266..d72a1f8 100644 --- a/src/nodepilot-ui/src/pages/MobileWorkflowView.tsx +++ b/src/nodepilot-ui/src/pages/MobileWorkflowView.tsx @@ -20,8 +20,10 @@ import { withDefaultEdgePorts } from '../lib/edgePorts'; import { NodeScaleOverrideContext } from '../components/designer/nodeScaleContext'; import { useWorkflowSignalR } from '../hooks/useSignalR'; -// Render the read-only phone graph at the `lg` node scale (NODE_SCALES index 3) — much bigger -// icons/labels than the desktop `xs` default, so a workflow stays legible on a small screen. +// Render the read-only phone graph at the `lg` node scale (NODE_SCALES index 3) — big enough +// icons/labels to keep a workflow legible on a small screen. This matches the desktop default +// today, but stays an explicit override: the phone view must not shrink when someone steps the +// desktop size down, since there is no size stepper on this screen to undo it. const MOBILE_SCALE_INDEX = 3; // Reuse the editor's node/edge renderers verbatim. ActivityNode reads everything it needs diff --git a/src/nodepilot-ui/src/pages/SystemSettingsPage.tsx b/src/nodepilot-ui/src/pages/SystemSettingsPage.tsx index fdbc637..f946ac5 100644 --- a/src/nodepilot-ui/src/pages/SystemSettingsPage.tsx +++ b/src/nodepilot-ui/src/pages/SystemSettingsPage.tsx @@ -27,16 +27,20 @@ type SubTab = 'integrations' | 'ai-knowledge' | 'retention' | 'system-info' // Tabs are progressively activated as their section is implemented. Disabled tabs // keep the operator informed about what's on the roadmap. +// `system-info` sits LAST on purpose: it is the only read-only tab here, so it belongs +// after everything that actually edits configuration rather than splitting that run. +// Order is presentation only — nothing indexes into this array, and `integrations` +// stays first because it is also the default/fallback section for a bare `?tab=system`. const TABS: { id: SubTab; ready: boolean }[] = [ { id: 'integrations', ready: true }, { id: 'ai-knowledge', ready: true }, { id: 'retention', ready: true }, - { id: 'system-info', ready: true }, { id: 'authentication', ready: true }, { id: 'logging-telemetry', ready: true }, { id: 'security', ready: true }, { id: 'performance', ready: true }, { id: 'db-admin', ready: true }, + { id: 'system-info', ready: true }, ]; const ICONS: Record> = { diff --git a/src/nodepilot-ui/src/stores/designStore.ts b/src/nodepilot-ui/src/stores/designStore.ts index 7cf46a2..d756860 100644 --- a/src/nodepilot-ui/src/stores/designStore.ts +++ b/src/nodepilot-ui/src/stores/designStore.ts @@ -137,7 +137,7 @@ export const useDesignStore = create()( setToolbarLayout: (toolbarLayout: ToolbarLayout) => set({ toolbarLayout }), nodeStyle: 'classic', nodeIconStyle: 'shape', - nodeScaleIndex: 1, // sm default — xs rendered 33-node graphs as an unreadable dot cloud + nodeScaleIndex: 3, // lg default — sm was legible but too small to actually author in labelFontOffsetIndex: 2, // 0-offset — use base labelFont from NODE_SCALES edgesAnimated: true, edgeWidthIndex: 2, // 2px default — matches previous hardcoded width @@ -190,12 +190,16 @@ export const useDesignStore = create()( }), { name: 'nodepilot-design', - version: 1, + version: 2, // Profiles that already used the full designer retain the previous surface. Fresh // profiles use the standard-mode default above. migrate: (persisted, version) => { - const state = persisted as Partial; - if (version < 1 && !state.designerMode) return { ...state, designerMode: 'expert' }; + let state = persisted as Partial; + if (version < 1 && !state.designerMode) state = { ...state, designerMode: 'expert' }; + // v2 raised the node-size default from sm (1) to lg (3). Only lift profiles that are + // still sitting on the OLD default — a stored index of anything else is a deliberate + // choice from the size stepper and must survive the bump. + if (version < 2 && state.nodeScaleIndex === 1) state = { ...state, nodeScaleIndex: 3 }; return state; }, },