feat(llm-security): playground v7.6.2-dev — prosjekt-surface opprydding + topbar-splitt [skip-docs]
- 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)
This commit is contained in:
parent
636bcb5824
commit
86d6ecdc50
1 changed files with 103 additions and 83 deletions
|
|
@ -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 +
|
||||
'<div class="app-header__spacer"></div>' +
|
||||
'<div class="app-header__actions" role="group" aria-label="Hovednavigasjon">' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-home">Hjem</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-catalog">Katalog</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-onboarding">Re-onboard</button>' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="export-state" aria-label="Eksporter state til JSON">Eksporter</button>' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="import-state" aria-label="Importer state fra JSON">Importer</button>' +
|
||||
'<input type="file" accept="application/json,.json" data-import-input hidden>' +
|
||||
'<button type="button" class="theme-toggle" data-action="toggle-theme" aria-label="Bytt til ' + themeNext + ' modus">' +
|
||||
'<span data-theme-label>' + themeLabel + '</span>' +
|
||||
'</button>' +
|
||||
'<div class="app-header__nav-group" role="group" aria-label="Primær navigasjon">' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-home">Hjem</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-catalog">Katalog</button>' +
|
||||
'<button type="button" class="btn btn--ghost btn--sm" data-action="goto-onboarding">Re-onboard</button>' +
|
||||
'</div>' +
|
||||
'<span class="app-header__nav-sep" aria-hidden="true"></span>' +
|
||||
'<div class="app-header__nav-group" role="group" aria-label="State og tema">' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="export-state" aria-label="Eksporter state til JSON">Eksporter</button>' +
|
||||
'<button type="button" class="btn btn--secondary btn--sm" data-action="import-state" aria-label="Importer state fra JSON">Importer</button>' +
|
||||
'<input type="file" accept="application/json,.json" data-import-input hidden>' +
|
||||
'<button type="button" class="theme-toggle" data-action="toggle-theme" aria-label="Bytt til ' + themeNext + ' modus">' +
|
||||
'<span data-theme-label>' + themeLabel + '</span>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</header>'
|
||||
);
|
||||
|
|
@ -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 = (
|
||||
'<div class="sub-zone">' +
|
||||
|
|
@ -7075,23 +7136,26 @@
|
|||
}
|
||||
|
||||
return (
|
||||
'<article class="card" data-command-subcard data-command-id="' + escapeAttr(cmd.id) + '">' +
|
||||
'<div class="card__head">' +
|
||||
'<div>' +
|
||||
'<article class="card command-subcard" data-command-subcard data-command-id="' + escapeAttr(cmd.id) + '">' +
|
||||
'<button type="button" class="card__head card__head--toggle" data-action="toggle-subcard" data-command="' + escapeAttr(cmd.id) + '" data-project-id="' + escapeAttr(projectId) + '" aria-expanded="' + (isExpanded ? 'true' : 'false') + '" aria-controls="' + escapeAttr(bodyId) + '">' +
|
||||
'<div class="card__head-text">' +
|
||||
'<h3 class="card__title">' + escapeHtml(cmd.label) + '</h3>' +
|
||||
'<p class="card__desc">' + escapeHtml(cmd.description) + '</p>' +
|
||||
'</div>' +
|
||||
'<div style="display:flex; flex-direction:column; gap:6px; align-items:flex-end;">' +
|
||||
'<div class="card__head-meta">' +
|
||||
'<span class="badge badge--scope-security">llm-security</span>' +
|
||||
(cmd.produces_report
|
||||
? '<span class="card__pill">' + (hasReport ? '✓ Rapport' : 'Rapport') + '</span>'
|
||||
: '<span class="card__pill">Verktøy</span>'
|
||||
) +
|
||||
'</div>' +
|
||||
'<span class="subcard-chev" aria-hidden="true">▾</span>' +
|
||||
'</button>' +
|
||||
'<div class="subcard-body" id="' + escapeAttr(bodyId) + '"' + (isExpanded ? '' : ' hidden') + '>' +
|
||||
formZone +
|
||||
pasteZone +
|
||||
reportZone +
|
||||
'</div>' +
|
||||
formZone +
|
||||
pasteZone +
|
||||
reportZone +
|
||||
'</article>'
|
||||
);
|
||||
}
|
||||
|
|
@ -7112,17 +7176,6 @@
|
|||
'</div>'
|
||||
);
|
||||
|
||||
const SCREENS = [
|
||||
{ id: 'oversikt', label: 'Oversikt' },
|
||||
{ id: 'rapporter', label: 'Rapporter' },
|
||||
{ id: 'kontekst', label: 'Kontekst' },
|
||||
{ id: 'eksport', label: 'Eksport' }
|
||||
];
|
||||
const screenTabsHtml = '<nav class="tab-list" role="tablist" aria-label="Prosjekt-skjermer">' + SCREENS.map(function (s) {
|
||||
const isActive = currentProjectScreen === s.id;
|
||||
return '<button type="button" class="tab" role="tab" aria-current="' + (isActive ? 'true' : 'false') + '" data-action="project-screen" data-screen="' + escapeAttr(s.id) + '">' + escapeHtml(s.label) + '</button>';
|
||||
}).join('') + '</nav>';
|
||||
|
||||
const tabsHtml = '<div class="project-tabs" role="tablist">' + CATALOG.categories.map(function (cat) {
|
||||
const isActive = currentProjectTab === cat.id;
|
||||
return '<button type="button" class="project-tab" role="tab"' + (isActive ? ' aria-current="true"' : '') + ' data-action="project-tab" data-tab="' + escapeAttr(cat.id) + '">' + escapeHtml(cat.label) + '<span class="project-tab__count">' + cat.count + '</span></button>';
|
||||
|
|
@ -7134,53 +7187,6 @@
|
|||
return '<div class="command-cards" role="tabpanel" data-tab-panel="' + escapeAttr(cat.id) + '"' + (isActive ? '' : ' hidden') + '>' + cards + '</div>';
|
||||
}).join('');
|
||||
|
||||
const scenarioChipsList = (project.scenarios || []).map(function (sid) {
|
||||
const s = SCENARIOS.find(function (x) { return x.id === sid; });
|
||||
return '<li>' + escapeHtml(s ? s.name : sid) + '</li>';
|
||||
}).join('');
|
||||
|
||||
const oversiktHtml = (
|
||||
'<div class="tab-panel" data-screen-id="oversikt"' + (currentProjectScreen === 'oversikt' ? '' : ' hidden') + '>' +
|
||||
'<div class="guide-panel guide-panel--info">' +
|
||||
'<div class="guide-panel__icon" aria-hidden="true">i</div>' +
|
||||
'<div class="guide-panel__body">' +
|
||||
'<h3 class="guide-panel__title">Oversikt</h3>' +
|
||||
'<p class="guide-panel__text">Opprettet ' + escapeHtml((project.createdAt || '').slice(0, 10)) + '. ' + reportFilled + ' av ' + reportTotal + ' rapporter generert.</p>' +
|
||||
'<p class="guide-panel__text" style="margin-top: var(--space-2);">Target: <code>' + escapeHtml(project.target_path || '—') + '</code> (<em>' + escapeHtml(project.target_type || 'codebase') + '</em>)</p>' +
|
||||
(scenarioChipsList ? '<p class="guide-panel__text" style="margin-top: var(--space-2);"><strong>Scenarioer:</strong></p><ul style="margin: 0; padding-left: var(--space-4); color: var(--color-text-secondary);">' + scenarioChipsList + '</ul>' : '') +
|
||||
'<p class="guide-panel__text" style="margin-top: var(--space-3);"><em>Fase 2-3: aggregert verdict-pille, top-funn på tvers av rapporter, og recommended-next-actions vises her.</em></p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
const rapporterHtml = '<div class="tab-panel" data-screen-id="rapporter"' + (currentProjectScreen === 'rapporter' ? '' : ' hidden') + '>' + tabsHtml + panelsHtml + '</div>';
|
||||
|
||||
const kontekstHtml = (
|
||||
'<div class="tab-panel" data-screen-id="kontekst"' + (currentProjectScreen === 'kontekst' ? '' : ' hidden') + '>' +
|
||||
'<div class="guide-panel guide-panel--info">' +
|
||||
'<div class="guide-panel__icon" aria-hidden="true">i</div>' +
|
||||
'<div class="guide-panel__body">' +
|
||||
'<h3 class="guide-panel__title">Kontekst</h3>' +
|
||||
'<p class="guide-panel__text">Fellesfeltene fra onboarding gjenbrukes automatisk i alle command-skjemaer. Bruk <button type="button" class="btn btn--ghost btn--sm" data-action="goto-onboarding" style="display:inline;">Re-onboard</button> for å oppdatere.</p>' +
|
||||
'<p class="guide-panel__text" style="margin-top: var(--space-2);"><em>Fase 2-3: snapshot av de 5 fellesgruppene og hvilke felt som prefilles per kommando vises her.</em></p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
const eksportHtml = (
|
||||
'<div class="tab-panel" data-screen-id="eksport"' + (currentProjectScreen === 'eksport' ? '' : ' hidden') + '>' +
|
||||
'<div class="guide-panel guide-panel--info">' +
|
||||
'<div class="guide-panel__icon" aria-hidden="true">i</div>' +
|
||||
'<div class="guide-panel__body">' +
|
||||
'<h3 class="guide-panel__title">Eksport</h3>' +
|
||||
'<p class="guide-panel__text">Bruk <strong>Eksporter</strong> i toppmenyen for hele state. Per-prosjekt PDF/Markdown-eksport kommer i Fase 3.</p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
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') }
|
||||
]
|
||||
},
|
||||
'<div class="stack-lg">' + actionBar + screenTabsHtml + oversiktHtml + rapporterHtml + kontekstHtml + eksportHtml + '</div>'
|
||||
'<div class="stack-lg">' + actionBar + tabsHtml + panelsHtml + '</div>'
|
||||
);
|
||||
|
||||
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');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue