From 1246b7a804a2ea88442ab853d525c4b967f0b056 Mon Sep 17 00:00:00 2001 From: davegilligan Date: Mon, 18 May 2026 19:08:16 +0200 Subject: [PATCH] feat(workbench): add DBN Case Workbench guided case-preparation hub Additive-only change: new workbench.php authenticated page with guided intake flow, evidence map, tool sequence, output checklist, and sessionStorage-only note persistence. Dashboard and public index get a new Case Workbench card. No existing tools, APIs, or prompts modified. Co-Authored-By: Claude Sonnet 4.6 --- assets/css/tools.css | 323 +++++++++++++++++++++++++++++++++++++++++ assets/js/workbench.js | 96 ++++++++++++ dashboard.php | 8 + includes/i18n.php | 115 +++++++++++++++ index.php | 12 ++ workbench.php | 206 ++++++++++++++++++++++++++ 6 files changed, 760 insertions(+) create mode 100644 assets/js/workbench.js create mode 100644 workbench.php diff --git a/assets/css/tools.css b/assets/css/tools.css index 8f69942..692a05a 100644 --- a/assets/css/tools.css +++ b/assets/css/tools.css @@ -6053,6 +6053,329 @@ body.lt-landing { .adv-preview-btn { font-size:.82rem; padding:5px 14px; border:1px solid var(--line); border-radius:5px; background:var(--bg); color:var(--muted); cursor:pointer; } .adv-preview-btn:hover { border-color:var(--teal); color:var(--teal); background:var(--soft-teal); } +/* Case Workbench - additive page */ +.workbench-shell { + max-width: 1480px; +} + +.workbench-dashboard-link { + text-decoration: none; +} + +.workbench-entry-art { + display: grid; + place-items: center; + background: + linear-gradient(135deg, rgba(0, 32, 91, 0.96), rgba(15, 118, 110, 0.86)), + repeating-linear-gradient(90deg, rgba(255,255,255,0.12) 0 1px, transparent 1px 28px); + color: #fff; + line-height: 1; +} + +.workbench-entry-art span { + display: inline-flex; + align-items: center; + justify-content: center; + width: 84px; + height: 84px; + border: 2px solid rgba(255,255,255,0.55); + border-radius: 50%; + font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; + font-size: 1.5rem; + font-weight: 800; + letter-spacing: 0.08em; + background: rgba(255,255,255,0.10); +} + +.workbench-hero { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.7fr); + gap: 18px; + align-items: stretch; + margin: 22px 0 18px; +} + +.workbench-hero__copy, +.workbench-hero__panel, +.workbench-panel { + border: 1px solid var(--dbn-line); + background: rgba(255,255,255,0.72); + box-shadow: 0 18px 45px rgba(25, 35, 52, 0.08); +} + +.workbench-hero__copy { + padding: clamp(24px, 4vw, 44px); +} + +.workbench-hero__copy h2 { + max-width: 860px; + margin: 0; + color: var(--dbn-ink); + font-family: 'Crimson Pro', serif; + font-size: clamp(2rem, 4vw, 4.4rem); + line-height: 0.94; + letter-spacing: 0; +} + +.workbench-hero__copy p:last-child { + max-width: 760px; + margin: 16px 0 0; + color: rgba(22, 19, 15, 0.74); + font-size: 1rem; + line-height: 1.7; +} + +.workbench-kicker { + margin: 0 0 10px; + color: var(--dbn-red); + font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.09em; + text-transform: uppercase; +} + +.workbench-hero__panel { + display: flex; + flex-direction: column; + justify-content: center; + gap: 12px; + padding: 24px; + border-left: 5px solid var(--dbn-teal); +} + +.workbench-hero__panel strong { + color: var(--dbn-blue); + font-size: 1.05rem; +} + +.workbench-hero__panel p { + margin: 0; + color: rgba(22, 19, 15, 0.70); + line-height: 1.6; +} + +.workbench-clear, +.workbench-tool-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 42px; + border: 0; + border-radius: 999px; + padding: 0 16px; + background: var(--dbn-blue); + color: #fff; + font-weight: 800; + text-decoration: none; + cursor: pointer; +} + +.workbench-clear { + align-self: flex-start; + background: var(--dbn-red); +} + +.workbench-status { + min-height: 20px; + color: var(--dbn-teal); + font-size: 0.86rem; + font-weight: 700; +} + +.workbench-grid { + display: grid; + grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); + gap: 18px; + margin-top: 18px; +} + +.workbench-panel { + padding: clamp(18px, 2.4vw, 28px); +} + +.workbench-panel--flow, +.workbench-panel--outputs { + grid-column: 1 / -1; +} + +.workbench-section-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-bottom: 18px; + border-bottom: 1px solid rgba(22, 19, 15, 0.12); + padding-bottom: 12px; +} + +.workbench-section-head h2 { + margin: 0; + color: var(--dbn-ink); + font-family: 'Crimson Pro', serif; + font-size: clamp(1.45rem, 2vw, 2.1rem); + letter-spacing: 0; +} + +.workbench-form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.workbench-field { + display: grid; + gap: 7px; + color: var(--dbn-ink); + font-weight: 800; +} + +.workbench-field--wide { + margin-top: 14px; +} + +.workbench-field span { + font-size: 0.86rem; +} + +.workbench-field input, +.workbench-field select, +.workbench-field textarea { + width: 100%; + border: 1px solid rgba(22, 19, 15, 0.18); + border-radius: 8px; + background: #fff; + color: var(--dbn-ink); + font: inherit; + font-weight: 500; + padding: 12px 13px; +} + +.workbench-field textarea { + min-height: 112px; + resize: vertical; + line-height: 1.5; +} + +.workbench-field input:focus, +.workbench-field select:focus, +.workbench-field textarea:focus, +.workbench-clear:focus, +.workbench-tool-link:focus, +.workbench-check input:focus { + outline: 3px solid rgba(15, 118, 110, 0.26); + outline-offset: 2px; +} + +.workbench-evidence-list { + display: grid; + gap: 14px; +} + +.workbench-flow { + display: grid; + grid-template-columns: repeat(7, minmax(160px, 1fr)); + gap: 12px; + overflow-x: auto; + padding-bottom: 4px; +} + +.workbench-step { + display: grid; + grid-template-rows: auto 1fr auto; + min-height: 276px; + border: 1px solid rgba(0, 32, 91, 0.14); + background: #fff; + padding: 16px; +} + +.workbench-step__index { + width: 42px; + height: 42px; + display: grid; + place-items: center; + border-radius: 50%; + background: var(--dbn-blue); + color: #fff; + font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; + font-weight: 900; +} + +.workbench-step__body { + padding: 16px 0; +} + +.workbench-step__body p, +.workbench-step__body span { + margin: 0; + color: rgba(22, 19, 15, 0.66); + font-size: 0.86rem; + line-height: 1.5; +} + +.workbench-step__body h3 { + margin: 6px 0 10px; + color: var(--dbn-ink); + font-size: 1.08rem; + line-height: 1.2; + letter-spacing: 0; +} + +.workbench-tool-link { + min-height: 38px; + font-size: 0.84rem; +} + +.workbench-checklist { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.workbench-check { + display: flex; + align-items: flex-start; + gap: 10px; + min-height: 72px; + border: 1px solid rgba(0, 32, 91, 0.14); + background: #fff; + padding: 14px; + color: var(--dbn-ink); + font-weight: 800; + line-height: 1.35; +} + +.workbench-check input { + width: 20px; + height: 20px; + margin-top: 1px; + accent-color: var(--dbn-teal); + flex: 0 0 auto; +} + +@media (max-width: 1080px) { + .workbench-hero, + .workbench-grid, + .workbench-checklist { + grid-template-columns: 1fr; + } + + .workbench-flow { + grid-template-columns: repeat(7, minmax(220px, 1fr)); + } +} + +@media (max-width: 640px) { + .workbench-form-grid { + grid-template-columns: 1fr; + } + + .workbench-section-head { + align-items: flex-start; + flex-direction: column; + } +} + /* Print styles */ @media print { .tool-rail, .reasoning-panel, .topbar, .tool-form, diff --git a/assets/js/workbench.js b/assets/js/workbench.js new file mode 100644 index 0000000..7143559 --- /dev/null +++ b/assets/js/workbench.js @@ -0,0 +1,96 @@ +(function () { + 'use strict'; + + const form = document.getElementById('workbenchForm'); + const clearButton = document.getElementById('workbenchClear'); + const status = document.getElementById('workbenchStatus'); + const storageKey = 'dbn.caseWorkbench.v1'; + let statusTimer = 0; + + if (!form) { + return; + } + + function setStatus(message) { + if (!status) { + return; + } + status.textContent = message; + window.clearTimeout(statusTimer); + statusTimer = window.setTimeout(function () { + status.textContent = ''; + }, 2200); + } + + function collect() { + const data = {}; + const fields = form.querySelectorAll('input, select, textarea'); + fields.forEach(function (field) { + if (!field.name) { + return; + } + if (field.type === 'checkbox') { + if (!Array.isArray(data[field.name])) { + data[field.name] = []; + } + if (field.checked) { + data[field.name].push(field.value); + } + return; + } + data[field.name] = field.value; + }); + data.saved_at = new Date().toISOString(); + return data; + } + + function restore(data) { + if (!data || typeof data !== 'object') { + return; + } + const fields = form.querySelectorAll('input, select, textarea'); + fields.forEach(function (field) { + if (!field.name) { + return; + } + if (field.type === 'checkbox') { + const values = Array.isArray(data[field.name]) ? data[field.name] : []; + field.checked = values.indexOf(field.value) !== -1; + return; + } + if (Object.prototype.hasOwnProperty.call(data, field.name)) { + field.value = data[field.name] || ''; + } + }); + } + + function save() { + try { + window.sessionStorage.setItem(storageKey, JSON.stringify(collect())); + setStatus('Saved in this browser session.'); + } catch (error) { + setStatus('Could not save this session locally.'); + } + } + + try { + const raw = window.sessionStorage.getItem(storageKey); + if (raw) { + restore(JSON.parse(raw)); + setStatus('Restored local workbench notes.'); + } + } catch (error) { + window.sessionStorage.removeItem(storageKey); + } + + form.addEventListener('input', save); + form.addEventListener('change', save); + + if (clearButton) { + clearButton.addEventListener('click', function () { + window.sessionStorage.removeItem(storageKey); + form.reset(); + setStatus('Workbench session cleared.'); + }); + } +}()); diff --git a/dashboard.php b/dashboard.php index 7b320db..face732 100644 --- a/dashboard.php +++ b/dashboard.php @@ -10,6 +10,7 @@ if (!dbnToolsIsAuthenticated()) { $uiLang = dbnToolsCurrentLanguage(); $tools = dbnToolsLaunchedTools($uiLang); +$workbench = dbnToolsWorkbenchMeta($uiLang); $langPath = '/dashboard.php'; require_once __DIR__ . '/includes/tool-svgs.php'; @@ -73,6 +74,13 @@ window.DBN_TOOLS_LANG = ;
+ + + +

+

+ +
$item): ?> diff --git a/includes/i18n.php b/includes/i18n.php index 8cebbfe..d08c7e1 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -318,6 +318,121 @@ function dbnToolsT(string $key, ?string $language = null): string return (string)($all[$language][$key] ?? $all['en'][$key] ?? $key); } +function dbnToolsWorkbenchCopy(?string $language = null): array +{ + $language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage()); + $copy = [ + 'en' => [ + 'title' => 'Case Workbench', + 'label' => 'Case Workbench', + 'sub' => 'Guided case flow', + 'description' => 'Plan a case, map evidence, and open the right legal tools in sequence without storing documents.', + 'badge' => 'Private session', + 'kicker' => 'Guided preparation', + 'hero_title' => 'One calm place to organise the next legal step.', + 'hero_text' => 'Use this workbench to frame the case, decide which tool to open next, and keep a local evidence map while your documents stay in memory by default.', + 'privacy_title' => 'Local notes only', + 'privacy_text' => 'This page saves notes in browser sessionStorage. It does not upload files or call tool APIs.', + 'clear_session' => 'Clear session', + 'all_tools' => 'All tools', + 'intake_title' => 'Case intake', + 'evidence_title' => 'Evidence map', + 'flow_title' => 'Recommended tool sequence', + 'outputs_title' => 'Output checklist', + 'role' => 'Who are you helping?', + 'choose' => 'Choose...', + 'role_parent' => 'Parent', + 'role_family' => 'Family member', + 'role_advocate' => 'Advocate or lawyer', + 'role_supporter' => 'Supporter', + 'case_type' => 'Case type', + 'case_barnevernet' => 'Barnevernet / child welfare', + 'case_custody' => 'Custody or residence', + 'case_access' => 'Contact / samvær', + 'case_echr' => 'ECHR / Strasbourg preparation', + 'deadline' => 'Next deadline', + 'language' => 'Working language', + 'main_concern' => 'Main concern', + 'main_concern_hint' => 'What decision, meeting, deadline, or document are you preparing for?', + 'documents' => 'Documents', + 'documents_hint' => 'List letters, decisions, reports, emails, or forms.', + 'meetings' => 'Meetings and audio', + 'meetings_hint' => 'List recordings, meeting notes, or conversations to transcribe.', + 'dates' => 'Dates and deadlines', + 'dates_hint' => 'List hearings, response deadlines, visits, decisions, and events.', + 'claims' => 'Claims and facts', + 'claims_hint' => 'List what the other side says, what you dispute, and what you can prove.', + 'missing' => 'Missing proof', + 'missing_hint' => 'List records, witnesses, messages, or sources still needed.', + 'bring_redact' => 'Bring back: safe text you can share with helpers.', + 'bring_transcribe' => 'Bring back: speaker notes, quotes, and follow-up issues.', + 'bring_timeline' => 'Bring back: dated events and urgent deadlines.', + 'bring_barnevernet' => 'Bring back: red flags, parties, and cited procedural issues.', + 'bring_research' => 'Bring back: legal angles, source excerpts, and uncertainty notes.', + 'bring_advocate' => 'Bring back: strongest brief sections and weak points.', + 'bring_corpus' => 'Bring back: source titles, sections, and exact citation context.', + 'open_tool' => 'Open tool', + 'output_lawyer' => 'Lawyer handoff pack', + 'output_barnevernet_response' => 'Barnevernet response preparation', + 'output_meeting_prep' => 'Meeting preparation note', + 'output_strasbourg' => 'Strasbourg / ECHR research prep', + 'next_step' => 'Next practical step', + 'next_step_hint' => 'Write the next action, owner, and deadline.', + ], + 'no' => [ + 'title' => 'Saksarbeidsbenk', + 'label' => 'Saksarbeidsbenk', + 'sub' => 'Veiledet saksflyt', + 'description' => 'Planlegg saken, kartlegg bevis og åpne riktig verktøy i riktig rekkefølge uten å lagre dokumenter.', + 'badge' => 'Privat økt', + 'kicker' => 'Veiledet forberedelse', + 'hero_title' => 'Ett rolig sted for neste juridiske steg.', + 'hero_text' => 'Bruk arbeidsbenken til å ramme inn saken, velge neste verktøy og holde et lokalt beviskart mens dokumenter behandles i minnet som standard.', + 'privacy_title' => 'Kun lokale notater', + 'privacy_text' => 'Denne siden lagrer notater i nettleserens sessionStorage. Den laster ikke opp filer og kaller ikke verktøy-API-er.', + ], + 'uk' => [ + 'title' => 'Робочий простір справи', + 'label' => 'Робочий простір справи', + 'sub' => 'Керований шлях справи', + 'description' => 'Плануйте справу, картуйте докази й відкривайте потрібні інструменти по черзі без збереження документів.', + 'badge' => 'Приватна сесія', + 'kicker' => 'Керована підготовка', + 'hero_title' => 'Одне спокійне місце для наступного юридичного кроку.', + 'hero_text' => 'Використовуйте цей простір, щоб описати справу, вибрати наступний інструмент і вести локальну карту доказів.', + 'privacy_title' => 'Лише локальні нотатки', + 'privacy_text' => 'Сторінка зберігає нотатки в sessionStorage браузера. Вона не завантажує файли і не викликає API інструментів.', + ], + 'pl' => [ + 'title' => 'Panel pracy nad sprawą', + 'label' => 'Panel pracy nad sprawą', + 'sub' => 'Prowadzony tok sprawy', + 'description' => 'Zaplanuj sprawę, uporządkuj dowody i otwieraj właściwe narzędzia po kolei bez zapisywania dokumentów.', + 'badge' => 'Prywatna sesja', + 'kicker' => 'Prowadzone przygotowanie', + 'hero_title' => 'Jedno spokojne miejsce na kolejny krok prawny.', + 'hero_text' => 'Użyj panelu, aby opisać sprawę, wybrać następne narzędzie i prowadzić lokalną mapę dowodów.', + 'privacy_title' => 'Tylko lokalne notatki', + 'privacy_text' => 'Strona zapisuje notatki w sessionStorage przeglądarki. Nie przesyła plików i nie wywołuje API narzędzi.', + ], + ]; + + return array_merge($copy['en'], $copy[$language] ?? []); +} + +function dbnToolsWorkbenchMeta(?string $language = null): array +{ + $copy = dbnToolsWorkbenchCopy($language); + return [ + 'label' => $copy['label'], + 'sub' => $copy['sub'], + 'description' => $copy['description'], + 'badge' => $copy['badge'], + 'url' => 'workbench.php', + 'icon' => 'WB', + ]; +} + function dbnToolsLaunchedTools(?string $language = null): array { $language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage()); diff --git a/index.php b/index.php index ada0773..7b0c7aa 100644 --- a/index.php +++ b/index.php @@ -47,6 +47,7 @@ if (isset($_GET['sso']) && !dbnToolsIsAuthenticated()) { $isAuthed = dbnToolsIsAuthenticated(); $userEmail = $isAuthed ? (string)($_SESSION['dbn_tools_user_email'] ?? '') : ''; $tools = dbnToolsLaunchedTools($uiLang); +$workbench = dbnToolsWorkbenchMeta($uiLang); $langPath = '/'; $toolsLogin = 'https://dobetternorge.no/tools-login.php?return=' . urlencode($returnPath); $registerUrl = 'https://dobetternorge.no/register.php'; @@ -147,6 +148,17 @@ window.DBN_TOOLS_LANG = ;

Seven purpose-built AI tools — from audio transcription to deep legal research. Every tool processes your documents in memory and discards them when you’re done.