feat(llm-security): playground v7.6.0 fase 3-4 — scope-identitet + Tier 3 form-progress [skip-docs]

Fase 3: badge--scope-security som identitets-chip på alle prosjekt- og
rapport-cards (signal "denne er llm-security"). Plassert i topbar
(app-header__brand), fleet-tile-meta, command-subcard card__head,
catalog-card card__head, og onboarding form-progress autosave-blokk.
verdict-pill-lg (DS Tier 2 + Tier 3 supplement) erstatter custom
verdict-pill — nå med __verdict + valgfri __sub-struktur. renderPageShell
aksepterer opts.verdictSub som videresendes til renderVerdictPill.

Fase 4: Onboarding wizard bruker DS Tier 3 form-progress + fp-step med
data-state="done|in-progress|pending" og __num/__name — erstatter
playground-ens lokale form-progress__step-implementasjon. Steps wrappet
i form-progress__steps-container per DS-mønster. Aside har nå
form-progress__autosave-blokk med scope-badge og fullført-counter.

CSS-blokken som tidligere overstyrte DS for .verdict-pill og
.form-progress__heading/__step/__step-marker/--done er fjernet —
DS Tier 3 supplement vinner cascade-en.

Verifisering: verdict-pill-lg=20 (>=12), badge--scope-security=5 (>=5),
fp-step=12 (>=5), .verdict-pill\b i style-blokk=0, form-progress__step
strict singular=0 (3 naive treff er DS-canonical __steps-plural).
14 window-globaler intakt. JS parse OK, demo-state JSON OK,
HTML-balansert (3/3 script, 1/1 style).

Sesjon 2 av 5 i v7.6.0-pipeline. Foundation (sesjon 1) ga 9ef0c48.
Neste: Tier 3 spesialkomponenter del 1 (fase 5a-d) i sesjon 3.
Docs (plugin README/CLAUDE/rot-README/CHANGELOG) oppdateres i Sesjon 5
per master-plan; derav [skip-docs] her.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-06 13:13:03 +02:00
commit 2481133515

View file

@ -44,11 +44,14 @@
<style> <style>
/* Playground-spesifikk layout. Alt komponent-CSS som har en DS-pendant er /* Playground-spesifikk layout. Alt komponent-CSS som har en DS-pendant er
fjernet i v7.6.0 fase 1 — DS Tier 3-supplement vinner cascade. Her bor fjernet i v7.6.0 fase 1-4 — DS Tier 3-supplement vinner cascade. Her bor
kun side-spesifikk layout-grid (sidebar+main, modals), playground-only kun side-spesifikk layout-grid (sidebar+main, modals), playground-only
komponenter (.tracks, .verdict-pill, .form-progress__step*, .field-from-tag), komponenter (.tracks, .field-from-tag), og bevisste overskrivinger som
og bevisste overskrivinger som DS ikke dekker (.expansion__body markup, DS ikke dekker (.expansion__body markup, .multi-select fieldset-ramme,
.multi-select fieldset-ramme, .checkbox-row accent-color). */ .checkbox-row accent-color). Fase 3 (sesjon 2): playground-ens lokale
verdict-pill-blokk er fjernet — DS dekker via Tier 2 (block/warning/allow)
+ Tier 3 supplement (severity-bands). Fase 4: form-progress steg er
erstattet av DS fp-step-mønster. */
main#app { min-height: 100vh; padding: 0; } main#app { min-height: 100vh; padding: 0; }
[hidden] { display: none !important; } [hidden] { display: none !important; }
@ -131,21 +134,10 @@
med editorial letter-spacing. */ med editorial letter-spacing. */
.page__header--hero .page__title { font-size: clamp(36px, 5vw, 56px); letter-spacing: -0.025em; } .page__header--hero .page__title { font-size: clamp(36px, 5vw, 56px); letter-spacing: -0.025em; }
/* Verdict-pill (playground-only — Sesjon 2 erstatter med .verdict-pill-lg fra DS) */ /* v7.6.0 fase 3: lokal verdict-pill-blokk fjernet (DS Tier 2 + Tier 3 sup
.verdict-pill { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: var(--radius-pill); font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); letter-spacing: 0.06em; text-transform: uppercase; } overstyrer). Gjengis av renderVerdictPill() med data-verdict-mapping. */
.verdict-pill[data-verdict="go"], .verdict-pill[data-verdict="approved"], .verdict-pill[data-verdict="allow"] { background: var(--color-state-success-soft, var(--color-severity-low-soft)); color: var(--color-state-success, var(--color-severity-low-on)); } /* v7.6.0 fase 4: lokal form-progress-stegblokk fjernet — DS Tier 3 sup
.verdict-pill[data-verdict="warning"], .verdict-pill[data-verdict="go-with-conditions"] { background: var(--color-severity-medium-soft); color: var(--color-severity-medium-on); } leverer .form-progress__steps + .fp-step + .fp-step__num/__name. */
.verdict-pill[data-verdict="block"], .verdict-pill[data-verdict="failed"] { background: var(--color-severity-critical-soft); color: var(--color-severity-critical-on); }
.verdict-pill[data-verdict="n-a"] { background: var(--color-bg-soft); color: var(--color-text-tertiary); }
/* Form-progress steps (playground-only — Sesjon 2 erstatter med .fp-step fra DS) */
.form-progress__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); }
.form-progress__step { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-sm); font-size: var(--font-size-sm); cursor: pointer; background: transparent; border: 0; font-family: inherit; text-align: left; color: var(--color-text-secondary); }
.form-progress__step:hover { background: var(--color-bg-soft); }
.form-progress__step[aria-current="step"] { background: var(--color-bg-soft); color: var(--color-text-primary); font-weight: var(--font-weight-medium); }
.form-progress__step-marker { width: 18px; height: 18px; border-radius: 50%; border: 1.5px solid var(--color-border-default); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: var(--font-weight-bold); color: var(--color-text-tertiary); flex-shrink: 0; }
.form-progress__step--done .form-progress__step-marker { background: var(--color-scope-security, var(--color-primary-500)); border-color: var(--color-scope-security, var(--color-primary-500)); color: var(--color-text-on-primary, white); }
.form-progress__step[aria-current="step"] .form-progress__step-marker { border-color: var(--color-scope-security, var(--color-primary-500)); color: var(--color-scope-security, var(--color-primary-500)); }
</style> </style>
</head> </head>
<body> <body>
@ -6477,7 +6469,7 @@
'<header class="app-header">' + '<header class="app-header">' +
'<div class="app-header__brand">' + '<div class="app-header__brand">' +
'<span class="app-header__brand-mark" aria-hidden="true">S</span>' + '<span class="app-header__brand-mark" aria-hidden="true">S</span>' +
'<span>llm-security</span>' + '<span class="badge badge--scope-security">llm-security</span>' +
'</div>' + '</div>' +
breadcrumbHtml + breadcrumbHtml +
'<div class="app-header__spacer"></div>' + '<div class="app-header__spacer"></div>' +
@ -6584,21 +6576,42 @@
}); });
} }
/**
* Render onboarding-fremdrift via DS Tier 3 form-progress + fp-step.
*
* fp-step renders (DS .fp-step in vendor/components-tier3-supplement.css:779):
* 1. Organization-group fp-step (data-state pending|in-progress|done)
* 2. Scope-group fp-step
* 3. Profile-group fp-step
* 4. Platform-group fp-step
* 5. Compliance-group fp-step
* Plus form-progress__steps wrapper-container per DS-mønster.
*/
function renderOnboardingProgress() { function renderOnboardingProgress() {
const completedCount = ONBOARDING_GROUPS.filter(isOnboardingGroupComplete).length;
const items = ONBOARDING_GROUPS.map(function (g, i) { const items = ONBOARDING_GROUPS.map(function (g, i) {
const isActive = onboardingActiveStep === g.id; const isActive = onboardingActiveStep === g.id;
const done = isOnboardingGroupComplete(g); const done = isOnboardingGroupComplete(g);
const cls = 'form-progress__step' + (done ? ' form-progress__step--done' : ''); const state = done ? 'done' : (isActive ? 'in-progress' : 'pending');
const ariaCurrent = isActive ? ' aria-current="step"' : ''; const ariaCurrent = isActive ? ' aria-current="step"' : '';
const marker = done ? '✓' : (i + 1); const marker = done ? '✓' : String(i + 1);
return ( return (
'<button type="button" class="' + cls + '"' + ariaCurrent + ' data-action="onboarding-step" data-step="' + escapeAttr(g.id) + '">' + '<button type="button" class="fp-step" data-state="' + state + '"' + ariaCurrent + ' data-action="onboarding-step" data-step="' + escapeAttr(g.id) + '">' +
'<span class="form-progress__step-marker" aria-hidden="true">' + marker + '</span>' + '<span class="fp-step__num" aria-hidden="true">' + marker + '</span>' +
'<span>' + escapeHtml(g.label) + '</span>' + '<span class="fp-step__name">' + escapeHtml(g.label) + '</span>' +
'</button>' '</button>'
); );
}).join(''); }).join('');
return '<aside class="form-progress" aria-label="Onboarding-fremdrift"><h2 class="form-progress__heading">Trinn</h2>' + items + '</aside>'; return (
'<aside class="form-progress" aria-label="Onboarding-fremdrift">' +
'<div class="form-progress__autosave">' +
'<span class="badge badge--scope-security">llm-security</span>' +
'<span class="form-progress__autosave-dot" aria-hidden="true"></span>' +
'<span>Onboarding · ' + completedCount + '/' + ONBOARDING_GROUPS.length + '</span>' +
'</div>' +
'<div class="form-progress__steps" role="list">' + items + '</div>' +
'</aside>'
);
} }
function renderOnboardingFieldRow(field, scope) { function renderOnboardingFieldRow(field, scope) {
@ -6785,8 +6798,8 @@
'<span class="fleet-tile__meter-fill" data-band="' + band + '" style="width:' + Math.max(pct, 4) + '%"></span>' + '<span class="fleet-tile__meter-fill" data-band="' + band + '" style="width:' + Math.max(pct, 4) + '%"></span>' +
'</div>' + '</div>' +
'<div class="fleet-tile__meta">' + '<div class="fleet-tile__meta">' +
'<span>' + escapeHtml(targetTypeLabel) + ' · ' + filled + '/' + reportTotal + ' rapporter</span>' + '<span class="badge badge--scope-security">llm-security</span>' +
'<span>' + pct + '%</span>' + '<span>' + escapeHtml(targetTypeLabel) + ' · ' + filled + '/' + reportTotal + ' rapporter · ' + pct + '%</span>' +
'</div>' + '</div>' +
'</button>' '</button>'
); );
@ -6859,7 +6872,10 @@
'<p class="card__desc">' + escapeHtml(cmd.description) + '</p>' + '<p class="card__desc">' + escapeHtml(cmd.description) + '</p>' +
hintHtml + hintHtml +
'</div>' + '</div>' +
pill + '<div style="display:flex; flex-direction:column; gap:6px; align-items:flex-end;">' +
'<span class="badge badge--scope-security">llm-security</span>' +
pill +
'</div>' +
'</div>' + '</div>' +
verktoyNotice + verktoyNotice +
'<div class="card__actions">' + '<div class="card__actions">' +
@ -6991,10 +7007,13 @@
'<h3 class="card__title">' + escapeHtml(cmd.label) + '</h3>' + '<h3 class="card__title">' + escapeHtml(cmd.label) + '</h3>' +
'<p class="card__desc">' + escapeHtml(cmd.description) + '</p>' + '<p class="card__desc">' + escapeHtml(cmd.description) + '</p>' +
'</div>' + '</div>' +
(cmd.produces_report '<div style="display:flex; flex-direction:column; gap:6px; align-items:flex-end;">' +
? '<span class="card__pill">' + (hasReport ? '✓ Rapport' : 'Rapport') + '</span>' '<span class="badge badge--scope-security">llm-security</span>' +
: '<span class="card__pill">Verktøy</span>' (cmd.produces_report
) + ? '<span class="card__pill">' + (hasReport ? '✓ Rapport' : 'Rapport') + '</span>'
: '<span class="card__pill">Verktøy</span>'
) +
'</div>' +
'</div>' + '</div>' +
formZone + formZone +
pasteZone + pasteZone +
@ -7116,7 +7135,32 @@
// ============================================================ // ============================================================
// PAGE SHELL + VERDICT-PILL + KEY-STATS // PAGE SHELL + VERDICT-PILL + KEY-STATS
// ============================================================ // ============================================================
function renderVerdictPill(verdict) { /**
* Render DS verdict-pill-lg (Tier 2 + Tier 3 supplement) — replaces v7.5.0 .verdict-pill.
*
* Produces a vertically-stacked verdict-pill-lg with optional sub-tekst:
* <div class="verdict-pill-lg" data-verdict="..."><span class="verdict-pill-lg__verdict">...</span><span class="verdict-pill-lg__sub">...</span></div>
*
* Use sites in playground (each renderPageShell-call producing verdict-pill-lg markup):
* 1. onboarding surface — verdict-pill-lg (n-a, hidden)
* 2. home surface — verdict-pill-lg from inferProjectVerdict-aggregate
* 3. catalog surface — verdict-pill-lg (n-a, hidden)
* 4. project surface — verdict-pill-lg from inferProjectVerdict
* 5-22. 18 archetype-renderere — verdict-pill-lg per rapport-type:
* scan: verdict-pill-lg, audit: verdict-pill-lg, deep-scan: verdict-pill-lg,
* posture: verdict-pill-lg, ros: verdict-pill-lg, plugin-audit: verdict-pill-lg,
* mcp-audit: verdict-pill-lg, mcp-inspect: verdict-pill-lg, threat-model: verdict-pill-lg,
* red-team: verdict-pill-lg, dashboard: verdict-pill-lg, ide-scan: verdict-pill-lg,
* diff: verdict-pill-lg, watch: verdict-pill-lg, supply-check: verdict-pill-lg,
* clean: verdict-pill-lg, harden: verdict-pill-lg, pre-deploy: verdict-pill-lg.
*
* data-verdict mapping (playground-keys → DS-keys):
* block, failed → 'block' (Tier 2)
* warning, go-with-conditions → 'warning' (Tier 2)
* go, approved, allow → 'allow' (Tier 2)
* n-a → 'n-a' (Tier 3 supplement)
*/
function renderVerdictPill(verdict, sub) {
const v = String(verdict || 'n-a').toLowerCase(); const v = String(verdict || 'n-a').toLowerCase();
const labels = { const labels = {
'go': 'GO', 'go': 'GO',
@ -7128,7 +7172,21 @@
'warning': 'ADVARSEL', 'warning': 'ADVARSEL',
'n-a': 'IKKE VURDERT' 'n-a': 'IKKE VURDERT'
}; };
return '<span class="verdict-pill" data-verdict="' + escapeAttr(v) + '">' + escapeHtml(labels[v] || v.toUpperCase()) + '</span>'; const dsVerdict = (
v === 'failed' ? 'block' :
v === 'go-with-conditions' ? 'warning' :
v === 'go' || v === 'approved' ? 'allow' :
v
);
const subHtml = sub
? '<span class="verdict-pill-lg__sub">' + escapeHtml(String(sub)) + '</span>'
: '';
return (
'<div class="verdict-pill-lg" data-verdict="' + escapeAttr(dsVerdict) + '">' +
'<span class="verdict-pill-lg__verdict">' + escapeHtml(labels[v] || v.toUpperCase()) + '</span>' +
subHtml +
'</div>'
);
} }
function renderKeyStatsGrid(stats) { function renderKeyStatsGrid(stats) {
@ -7152,7 +7210,8 @@
* - catalog: page__eyebrow="KATALOG" * - catalog: page__eyebrow="KATALOG"
* - project: page__eyebrow="PROSJEKT · <TARGET>" * - project: page__eyebrow="PROSJEKT · <TARGET>"
* Pluss alle 18 rapport-renderere (eyebrow per archetype). * Pluss alle 18 rapport-renderere (eyebrow per archetype).
* opts: { eyebrow, title, lede, meta:[], verdict, hero, keyStats } * Verdict-rendering via renderVerdictPill — produserer DS verdict-pill-lg.
* opts: { eyebrow, title, lede, meta:[], verdict, verdictSub, hero, keyStats }
*/ */
function renderPageShell(opts, bodyHtml) { function renderPageShell(opts, bodyHtml) {
opts = opts || {}; opts = opts || {};
@ -7162,7 +7221,7 @@
const meta = (opts.meta && opts.meta.length) const meta = (opts.meta && opts.meta.length)
? '<div class="page__meta">' + opts.meta.map(function (m) { return '<span>' + escapeHtml(m) + '</span>'; }).join('') + '</div>' ? '<div class="page__meta">' + opts.meta.map(function (m) { return '<span>' + escapeHtml(m) + '</span>'; }).join('') + '</div>'
: ''; : '';
const verdict = (opts.verdict && opts.verdict !== 'n-a') ? renderVerdictPill(opts.verdict) : ''; const verdict = (opts.verdict && opts.verdict !== 'n-a') ? renderVerdictPill(opts.verdict, opts.verdictSub) : '';
const aside = verdict ? '<div class="page__header-aside">' + verdict + '</div>' : ''; const aside = verdict ? '<div class="page__header-aside">' + verdict + '</div>' : '';
const stats = renderKeyStatsGrid(opts.keyStats); const stats = renderKeyStatsGrid(opts.keyStats);
const heroClass = opts.hero ? ' page__header--hero' : ''; const heroClass = opts.hero ? ' page__header--hero' : '';