From 86d6ecdc50199b65fafb841654bbfa7f59a30793 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Mon, 18 May 2026 12:23:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(llm-security):=20playground=20v7.6.2-dev?= =?UTF-8?q?=20=E2=80=94=20prosjekt-surface=20opprydding=20+=20topbar-split?= =?UTF-8?q?t=20[skip-docs]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderCommandSubCard: collapsed-by-default + click-to-expand uten remount - renderProjectSurface: stub-screens (Oversikt/Kontekst/Eksport) fjernet, kun Rapporter-tab - renderTopbar: split-pattern (primær nav venstre / state-IO høyre) --- .../playground/llm-security-playground.html | 186 ++++++++++-------- 1 file changed, 103 insertions(+), 83 deletions(-) diff --git a/plugins/llm-security/playground/llm-security-playground.html b/plugins/llm-security/playground/llm-security-playground.html index 807bc4a..36d7d36 100644 --- a/plugins/llm-security/playground/llm-security-playground.html +++ b/plugins/llm-security/playground/llm-security-playground.html @@ -78,6 +78,55 @@ .command-cards { display: flex; flex-direction: column; gap: var(--space-4); } .sub-zone { border-top: 1px solid var(--color-border-subtle); padding-top: var(--space-3); } .sub-zone__heading { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); margin: 0 0 var(--space-2); } + + /* Collapsible command sub-cards (Rapporter-tab) */ + .command-subcard { padding: 0; overflow: hidden; } + .command-subcard .card__head--toggle { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-3); + width: 100%; + margin: 0; + padding: var(--space-4) var(--space-5); + background: transparent; + border: 0; + cursor: pointer; + text-align: left; + font-family: inherit; + color: inherit; + } + .command-subcard .card__head--toggle:hover { background: var(--color-bg-soft); } + .command-subcard .card__head--toggle:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: -2px; } + .command-subcard .card__head-text { flex: 1; min-width: 0; } + .command-subcard .card__head-meta { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; flex-shrink: 0; } + .command-subcard .subcard-chev { + display: inline-block; + font-size: 0.875rem; + color: var(--color-text-tertiary); + transform: rotate(-90deg); + transition: transform 0.15s ease; + align-self: center; + flex-shrink: 0; + width: 1em; + text-align: center; + } + .command-subcard .card__head--toggle[aria-expanded="true"] .subcard-chev { transform: rotate(0deg); } + .command-subcard .subcard-body { + padding: 0 var(--space-5) var(--space-5); + display: flex; + flex-direction: column; + gap: var(--space-3); + } + + /* App header — split nav groups */ + .app-header__nav-group { display: flex; align-items: center; gap: var(--space-2); } + .app-header__nav-sep { + width: 1px; + align-self: stretch; + background: var(--color-border-subtle); + margin: 0 var(--space-2); + } .paste-import-row { display: flex; flex-direction: column; gap: var(--space-2); } .paste-import-row__actions { display: flex; gap: var(--space-2); align-items: center; } .form-zone-placeholder { padding: var(--space-3); background: var(--color-bg-soft); border-radius: var(--radius-sm); font-size: var(--font-size-sm); color: var(--color-text-tertiary); font-style: italic; } @@ -6532,15 +6581,20 @@ breadcrumbHtml + '
' + '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '
' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '' + + '' + + '' + + '
' + '
' + '' ); @@ -7029,12 +7083,19 @@ // PROJECT SURFACE (stub i Fase 1 — full report-render i Fase 2/3) // ============================================================ let currentProjectTab = 'discover'; - let currentProjectScreen = 'rapporter'; + + // Tracks which sub-cards are expanded — key: projectId + '::' + cmdId. + // Persists across re-renders so paste-import etc. doesn't collapse them. + const expandedSubcards = new Set(); + + function subcardKey(projectId, cmdId) { return projectId + '::' + cmdId; } function renderCommandSubCard(cmd, projectId) { const project = findProject(projectId); const report = project && project.reports && project.reports[cmd.id]; const hasReport = !!(report && report.parsed); + const isExpanded = expandedSubcards.has(subcardKey(projectId, cmd.id)); + const bodyId = 'subcard-body-' + cmd.id.replace(/[^a-zA-Z0-9_-]/g, '_'); const formZone = ( '
' + @@ -7075,23 +7136,26 @@ } return ( - '
' + - '
' + - '
' + + '
' + + '' + + '
' + + formZone + + pasteZone + + reportZone + '
' + - formZone + - pasteZone + - reportZone + '
' ); } @@ -7112,17 +7176,6 @@ '
' ); - const SCREENS = [ - { id: 'oversikt', label: 'Oversikt' }, - { id: 'rapporter', label: 'Rapporter' }, - { id: 'kontekst', label: 'Kontekst' }, - { id: 'eksport', label: 'Eksport' } - ]; - const screenTabsHtml = ''; - const tabsHtml = '
' + CATALOG.categories.map(function (cat) { const isActive = currentProjectTab === cat.id; return ''; @@ -7134,53 +7187,6 @@ return '
' + cards + '
'; }).join(''); - const scenarioChipsList = (project.scenarios || []).map(function (sid) { - const s = SCENARIOS.find(function (x) { return x.id === sid; }); - return '
  • ' + escapeHtml(s ? s.name : sid) + '
  • '; - }).join(''); - - const oversiktHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Oversikt

    ' + - '

    Opprettet ' + escapeHtml((project.createdAt || '').slice(0, 10)) + '. ' + reportFilled + ' av ' + reportTotal + ' rapporter generert.

    ' + - '

    Target: ' + escapeHtml(project.target_path || '—') + ' (' + escapeHtml(project.target_type || 'codebase') + ')

    ' + - (scenarioChipsList ? '

    Scenarioer:

      ' + scenarioChipsList + '
    ' : '') + - '

    Fase 2-3: aggregert verdict-pille, top-funn på tvers av rapporter, og recommended-next-actions vises her.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - - const rapporterHtml = '
    ' + tabsHtml + panelsHtml + '
    '; - - const kontekstHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Kontekst

    ' + - '

    Fellesfeltene fra onboarding gjenbrukes automatisk i alle command-skjemaer. Bruk for å oppdatere.

    ' + - '

    Fase 2-3: snapshot av de 5 fellesgruppene og hvilke felt som prefilles per kommando vises her.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - - const eksportHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Eksport

    ' + - '

    Bruk Eksporter i toppmenyen for hele state. Per-prosjekt PDF/Markdown-eksport kommer i Fase 3.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - const projectShell = renderPageShell({ eyebrow: 'PROSJEKT · ' + escapeHtml((project.target_type || 'codebase').toUpperCase()), title: project.name, @@ -7197,7 +7203,7 @@ { label: 'TARGET', value: (project.target_type || 'codebase') } ] }, - '
    ' + actionBar + screenTabsHtml + oversiktHtml + rapporterHtml + kontekstHtml + eksportHtml + '
    ' + '
    ' + actionBar + tabsHtml + panelsHtml + '
    ' ); root.innerHTML = renderTopbar('Prosjekt: ' + escapeHtml(project.name)) + @@ -10572,22 +10578,37 @@ } // Project tabs - if (action === 'project-screen') { - currentProjectScreen = target.dataset.screen; - scheduleRender(); - return; - } if (action === 'project-tab') { currentProjectTab = target.dataset.tab; scheduleRender(); return; } + // Sub-card toggle (Rapporter-tab) — direct DOM manipulation to preserve form-field state + if (action === 'toggle-subcard') { + const cmdId = target.dataset.command; + const projectId = target.dataset.projectId; + const article = target.closest('[data-command-subcard]'); + const body = article ? article.querySelector('.subcard-body') : null; + if (!body) return; + const key = projectId + '::' + cmdId; + const willExpand = body.hasAttribute('hidden'); + if (willExpand) { + expandedSubcards.add(key); + body.removeAttribute('hidden'); + target.setAttribute('aria-expanded', 'true'); + } else { + expandedSubcards.delete(key); + body.setAttribute('hidden', ''); + target.setAttribute('aria-expanded', 'false'); + } + return; + } + // Project lifecycle if (action === 'open-project') { const pid = target.dataset.projectId; store.state.activeProjectId = pid; - currentProjectScreen = 'rapporter'; currentProjectTab = 'discover'; navigate('project'); return; @@ -10633,7 +10654,6 @@ }; store.state.projects.push(project); store.state.activeProjectId = project.id; - currentProjectScreen = 'rapporter'; currentProjectTab = 'discover'; closeModal(); navigate('project');