diff --git a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html
index 35ef621..923a463 100644
--- a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html
+++ b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html
@@ -1337,18 +1337,338 @@
);
}
- // Stub for project + catalog som fylles i Steps 7, 9.
+ // ============================================================
+ // PROJECT SURFACE (Step 7)
+ // ============================================================
+ //
+ // Per-prosjekt detalj: header med navn + scenario-chips, 5 kategori-tabs
+ // (én per CATALOG-kategori), command-kort i hver tab. Sub-zones per kort:
+ // 1. Skjema-zone — placeholder (Step 8 fyller med renderCommandForm)
+ // 2. Paste-import — KUN for produces_report=true (textarea + parse-knapp)
+ // 3. Rapport-slot — KUN for produces_report=true (data-report-slot)
+ // Verktøy-commands får skjema-zone + .guide-panel--info 'Verktøy'-notis.
+ //
+ // Prosjekt-opprettelse via modal (createProjectFromModal). projectId =
+ // crypto.randomUUID. Sletting via .error-summary-modal med eksplisitt
+ // bekreftelse.
+ //
+ // Active-tab er transient (modul-lokal currentProjectTab) så export-state
+ // ikke forurenses av UI-state. Default 'regulatory' ved hver project-enter.
+
+ // 8 scenarioer fra v2 — gjenbrukes som scenario-tags på prosjekter.
+ const SCENARIOS = [
+ { id: 'rag-chatbot', name: 'RAG-chatbot for interne dokumenter' },
+ { id: 'autonomous-agent', name: 'Autonom agent for saksbehandling' },
+ { id: 'document-classification', name: 'Dokumentklassifisering og -prosessering' },
+ { id: 'multi-agent', name: 'Multi-agent workflow' },
+ { id: 'copilot-extension', name: 'Copilot-utvidelse for M365' },
+ { id: 'customer-service', name: 'Kundeservice-chatbot' },
+ { id: 'intelligent-search', name: 'Intelligent søk på tvers av fagsystemer' },
+ { id: 'reporting', name: 'AI-assistert rapportering' }
+ ];
+
+ let currentProjectTab = 'regulatory';
+
+ function findProject(id) {
+ const list = store.state.projects || [];
+ for (let i = 0; i < list.length; i++) {
+ if (list[i].id === id) return list[i];
+ }
+ return null;
+ }
+
+ function createProject(data) {
+ const id = (typeof crypto !== 'undefined' && crypto.randomUUID)
+ ? crypto.randomUUID()
+ : 'p-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
+ const project = {
+ id: id,
+ name: data.name || 'Uten navn',
+ description: data.description || '',
+ scenarios: Array.isArray(data.scenarios) ? data.scenarios.slice() : [],
+ createdAt: new Date().toISOString(),
+ reports: {} // commandId → { input: {...}, raw_markdown: '', parsed: {...} }
+ };
+ // Push via Proxy så change-event fyres og persistens skedules.
+ store.state.projects.push(project);
+ store.state.activeProjectId = id;
+ currentProjectTab = 'regulatory';
+ return project;
+ }
+
+ function deleteProject(id) {
+ const list = store.state.projects;
+ for (let i = 0; i < list.length; i++) {
+ if (list[i].id === id) {
+ list.splice(i, 1);
+ break;
+ }
+ }
+ if (store.state.activeProjectId === id) store.state.activeProjectId = null;
+ }
+
+ // ---- Modal infrastructure ----
+
+ function mountModal(html) {
+ unmountModal();
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = html;
+ const node = wrapper.firstElementChild;
+ if (!node) return;
+ node.setAttribute('data-modal-root', 'true');
+ document.body.appendChild(node);
+ // Klikk på backdrop (selve roten) lukker; klikk inni .modal bobler ikke til root.
+ node.addEventListener('click', function (ev) {
+ if (ev.target === node) unmountModal();
+ });
+ // Esc lukker
+ function escHandler(ev) {
+ if (ev.key === 'Escape') {
+ unmountModal();
+ document.removeEventListener('keydown', escHandler);
+ }
+ }
+ document.addEventListener('keydown', escHandler);
+ // Fokuser første input
+ setTimeout(function () {
+ const first = node.querySelector('input, select, textarea, button');
+ if (first && first.focus) first.focus();
+ }, 0);
+ }
+
+ function unmountModal() {
+ const existing = document.querySelector('[data-modal-root]');
+ if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
+ }
+
+ function renderNewProjectModalHtml() {
+ const scenarioOptions = SCENARIOS.map(function (s, i) {
+ return (
+ ''
+ );
+ }).join('');
+ return (
+ '
' +
+ '
' +
+ '
Nytt prosjekt
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ 'Scenario-tagging' +
+ '
' +
+ 'Brukes for sammenligning og pipeline-anbefalinger.' +
+ '' +
+ '
' +
+ '
Mangler input
' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
'
+ );
+ }
+
+ function renderDeleteProjectModalHtml(project) {
+ const reportCount = projectReportCount(project);
+ return (
+ '' +
+ '
' +
+ '
Slett prosjekt?
' +
+ '
' +
+ '
Bekreft sletting
' +
+ '
' +
+ '
Dette fjerner prosjektet ' + escapeHtml(project.name) + ' og ' + reportCount + ' importert' + (reportCount === 1 ? '' : 'e') + ' rapport' + (reportCount === 1 ? '' : 'er') + '. Handlingen kan ikke angres.
' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
'
+ );
+ }
+
+ // ---- Sub-card rendering ----
+
+ function renderCommandSubCard(cmd) {
+ const titleHtml = (
+ '' +
+ '
' +
+ '
' + escapeHtml(cmd.label) + '
' +
+ '
' + escapeHtml(cmd.description) + '
' +
+ '
' +
+ '
/architect:' + escapeHtml(cmd.id) + '' +
+ '
'
+ );
+
+ const formZone = (
+ '' +
+ '
Skjema
' +
+ '
Skjema-renderer kommer i Step 8 (' + cmd.input_fields.length + ' felter, ' + (cmd.input_fields.filter(function (f) { return f.from === 'shared'; }).length) + ' fra shared).
' +
+ '
'
+ );
+
+ if (!cmd.produces_report) {
+ // Verktøy: skjema-zone + .guide-panel--info notis
+ const toolNotice = (
+ '' +
+ '
' +
+ '
i
' +
+ '
' +
+ '
Verktøy
' +
+ '
Dette er et verktøy. Ingen rapport-import — bruk skjemaet til å bygge en pipeline-streng som kjøres i terminalen.
' +
+ '
' +
+ '
' +
+ '
'
+ );
+ return (
+ '' +
+ titleHtml +
+ formZone +
+ toolNotice +
+ ''
+ );
+ }
+
+ // Rapport-produserende: skjema-zone + paste-import-zone + report-zone
+ const pasteZone = (
+ '' +
+ '
Lim inn rapport-output
' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ 'Routes via PARSERS[' + escapeHtml(cmd.report_archetype || '?') + '] → ' + escapeHtml(cmd.renderer || '?') + ' (Step 11/12).' +
+ '
' +
+ '
' +
+ '
'
+ );
+
+ const reportZone = (
+ '' +
+ '
Visualisering
' +
+ '
' +
+ '
'
+ );
+
+ return (
+ '' +
+ titleHtml +
+ formZone +
+ pasteZone +
+ reportZone +
+ ''
+ );
+ }
+
function renderProjectSurface() {
const root = getSurfaceEl('project');
if (!root) return;
- root.innerHTML = renderTopbar('Prosjekt') + 'Prosjekt-detaljvisning fylles i Step 7.
';
+
+ const project = findProject(store.state.activeProjectId);
+ if (!project) {
+ // Mistet aktivt prosjekt — fall tilbake til hjem.
+ navigate('home');
+ return;
+ }
+
+ const reportTotal = CATALOG.commands.filter(function (c) { return c.produces_report; }).length;
+ const reportFilled = projectReportCount(project);
+
+ const scenarioChips = (project.scenarios || []).map(function (sid) {
+ const s = SCENARIOS.find(function (x) { return x.id === sid; });
+ return '';
+ }).join('');
+ const dateChip = '';
+ const progressChip = '';
+
+ const headerHtml = (
+ ''
+ );
+
+ // Tabs per CATALOG.categories
+ const tabsHtml = '' + CATALOG.categories.map(function (cat) {
+ const isActive = currentProjectTab === cat.id;
+ return (
+ ''
+ );
+ }).join('') + '
';
+
+ // Render ALLE kategori-paneler i DOM (med [hidden] på inaktive). Dette
+ // sikrer at querySelectorAll('[data-paste-import]') matcher alle 17
+ // rapport-produserende commands uavhengig av aktiv tab.
+ const panelsHtml = CATALOG.categories.map(function (cat) {
+ const isActive = currentProjectTab === cat.id;
+ const cards = CATALOG.commands
+ .filter(function (c) { return c.category === cat.id; })
+ .map(renderCommandSubCard).join('');
+ return (
+ '' +
+ cards +
+ '
'
+ );
+ }).join('');
+
+ root.innerHTML = (
+ renderTopbar('Prosjekt: ' + escapeHtml(project.name)) +
+ '' +
+ headerHtml +
+ tabsHtml +
+ panelsHtml +
+ '
'
+ );
}
+
function renderCatalogStub() {
const root = getSurfaceEl('catalog');
if (!root) return;
root.innerHTML = renderTopbar('Katalog') + 'Command-katalog fylles i Step 9.
';
}
+ // ---- Paste-import stub (Step 12 erstatter med faktisk routing) ----
+
+ function handlePasteImport(commandId, markdown) {
+ // Stub: logger til konsoll for å verifisere DOM-kontrakten. Step 12
+ // henter PARSERS[CATALOG[id].report_archetype] + RENDERERS[id], parser
+ // markdown og injiserer i [data-report-slot=""].
+ console.log('parse-pending:', commandId, (markdown || '').slice(0, 80));
+ const slot = document.querySelector('[data-report-slot="' + commandId + '"]');
+ if (slot) {
+ slot.innerHTML = '⏳
Markdown mottatt (' + (markdown || '').length + ' tegn). Parser+renderer kommer i Step 12.
';
+ }
+ }
+ window.__handlePasteImport = handlePasteImport;
+
// ============================================================
// ONBOARDING SURFACE (Step 5)
// ============================================================
@@ -1760,11 +2080,89 @@
navigate('project');
};
- // Stub — Step 7 erstatter med modal-åpning.
ACTIONS['new-project'] = function () {
- console.log('[playground v3] new-project: modal kommer i Step 7');
+ mountModal(renderNewProjectModalHtml());
};
+ ACTIONS['modal-cancel'] = function () { unmountModal(); };
+
+ ACTIONS['create-project'] = function () {
+ const modal = document.querySelector('[data-modal-root]');
+ if (!modal) return;
+ const nameEl = modal.querySelector('[data-new-project-field="name"]');
+ const descEl = modal.querySelector('[data-new-project-field="description"]');
+ const errBox = modal.querySelector('[data-new-project-errors]');
+ const errText = modal.querySelector('[data-new-project-error-text]');
+ const name = nameEl ? String(nameEl.value || '').trim() : '';
+ const description = descEl ? String(descEl.value || '').trim() : '';
+ if (!name) {
+ if (errBox && errText) {
+ errBox.hidden = false;
+ errText.textContent = 'Prosjektnavn er påkrevd.';
+ }
+ if (nameEl) nameEl.focus();
+ return;
+ }
+ const scenarios = Array.from(modal.querySelectorAll('[data-new-project-scenario]'))
+ .filter(function (cb) { return cb.checked; })
+ .map(function (cb) { return cb.value; });
+ createProject({ name: name, description: description, scenarios: scenarios });
+ unmountModal();
+ navigate('project');
+ };
+
+ ACTIONS['delete-project'] = function (ev, el) {
+ const id = el.dataset.projectId;
+ const project = findProject(id);
+ if (!project) return;
+ mountModal(renderDeleteProjectModalHtml(project));
+ };
+
+ ACTIONS['confirm-delete-project'] = function (ev, el) {
+ const id = el.dataset.projectId;
+ if (!id) return;
+ deleteProject(id);
+ unmountModal();
+ navigate('home');
+ };
+
+ ACTIONS['project-tab'] = function (ev, el) {
+ const tab = el.dataset.tab;
+ if (!tab) return;
+ currentProjectTab = tab;
+ // Toggle visning uten full re-render (bevarer textarea-input).
+ const root = getSurfaceEl('project');
+ if (!root) return;
+ const tabs = root.querySelectorAll('.project-tab');
+ tabs.forEach(function (t) {
+ if (t.dataset.tab === tab) t.setAttribute('aria-current', 'true');
+ else t.removeAttribute('aria-current');
+ });
+ const panels = root.querySelectorAll('[data-tab-panel]');
+ panels.forEach(function (p) {
+ p.hidden = (p.dataset.tabPanel !== tab);
+ });
+ };
+
+ ACTIONS['parse'] = function (ev, el) {
+ const commandId = el.dataset.command;
+ if (!commandId) return;
+ const root = getSurfaceEl('project');
+ if (!root) return;
+ const textarea = root.querySelector('[data-paste-import="' + commandId + '"]');
+ if (!textarea) return;
+ const markdown = textarea.value || '';
+ handlePasteImport(commandId, markdown);
+ };
+
+ // Eksponer for Verify-asserts og Step 8/12.
+ window.__SCENARIOS = SCENARIOS;
+ window.__createProject = createProject;
+ window.__deleteProject = deleteProject;
+ window.__findProject = findProject;
+ window.__mountModal = mountModal;
+ window.__unmountModal = unmountModal;
+
ACTIONS['export-state'] = function () {
try { exportState(); }
catch (err) { console.error('[playground v3] export feilet:', err); alert('Eksport feilet: ' + err.message); }