frontend: High-Risk-Warndreieck + Prod-Gating der Test-Hilfen
- [!]-Knoten trägt jetzt ein Warndreieck (⚠) an der oberen linken Ecke, zusätzlich zur orangen Färbung — im Diagramm, im SVG/PNG-Export und barrierefrei (aria-hidden, Status steckt im aria-label). Tooltip in allen 9 Sprachen. - SPEC §4: [!]/highrisk-Status nachgetragen (war beim ersten Hinzufügen übersprungen), inkl. Warndreieck-Kennzeichnung. - Beispiel-PSP: Backend als High-Risk ([!]) markiert. - Debug-Panel + Reset-Button nur außerhalb des Prod-Builds (isProdBuild via VITE_BUILD_BADGE); im build:prod eliminiert esbuild den Zweig als toten Code. Update-Check + Banner bleiben in allen Builds aktiv. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
90b5deb45b
commit
095dbfb63e
+28
-5
@@ -16,7 +16,7 @@ const INITIAL = `%% Project structure – Sprint 14
|
||||
- [/] Web (S)
|
||||
- [ ] Android (M)
|
||||
- [ ] iOS (M)
|
||||
- [ ] Backend (L) @ben @carla
|
||||
- [!] Backend (L) @ben @carla
|
||||
- [ ] CMS integration (M)
|
||||
| [ ] WordPress
|
||||
| [?] Headless CMS
|
||||
@@ -195,10 +195,16 @@ function diagramToSvg(){
|
||||
const dashed = cs.borderTopStyle === 'dashed';
|
||||
parts.push(`<rect x="${b.x.toFixed(1)}" y="${b.y.toFixed(1)}" width="${b.w.toFixed(1)}" height="${b.h.toFixed(1)}" rx="8" fill="${cs.backgroundColor}" stroke="${cs.borderTopColor}" stroke-width="${parseFloat(cs.borderTopWidth)||1.5}"${dashed?' stroke-dasharray="4 3"':''}/>`);
|
||||
const clone = node.cloneNode(true);
|
||||
clone.querySelectorAll('.size,.tags,.ext').forEach(e => e.remove());
|
||||
clone.querySelectorAll('.size,.tags,.ext,.risk').forEach(e => e.remove());
|
||||
const label = clone.textContent.replace(/\s+/g,' ').trim();
|
||||
const deco = cs.textDecorationLine.includes('line-through') ? ' text-decoration="line-through"' : '';
|
||||
parts.push(`<text x="${b.cx.toFixed(1)}" y="${(b.cy+5).toFixed(1)}" text-anchor="middle" fill="${cs.color}" font-size="14" font-weight="${cs.fontWeight}"${deco}>${esc(label)}</text>`);
|
||||
const riskEl = node.querySelector('.risk');
|
||||
if(riskEl){
|
||||
const rb = R(riskEl);
|
||||
parts.push(`<circle cx="${rb.cx.toFixed(1)}" cy="${rb.cy.toFixed(1)}" r="${(Math.min(rb.w,rb.h)/2).toFixed(1)}" fill="#ffffff" stroke="#F97316" stroke-width="1.5"/>`);
|
||||
parts.push(`<text x="${rb.cx.toFixed(1)}" y="${(rb.cy+3.5).toFixed(1)}" text-anchor="middle" fill="#F97316" font-size="10">⚠︎</text>`);
|
||||
}
|
||||
const sizeEl = node.querySelector('.size');
|
||||
if(sizeEl) drawBadge(sizeEl, '#0F766E', '#ffffff');
|
||||
node.querySelectorAll('.tag').forEach(tg => {
|
||||
@@ -559,6 +565,7 @@ const I18N = {
|
||||
privacy:"Datenschutz",
|
||||
legendTooltip:"Legende ein-/ausblenden",
|
||||
ghostTooltip:"Ab Größe M sollte ein Element weiter untergliedert werden.",
|
||||
riskTooltip:"High Risk – Aufwand noch unklar.",
|
||||
discardedTooltip:"Verworfene Knoten samt Teilbaum ein-/ausblenden",
|
||||
cheapTooltip:"Günstigsten Pfad hervorheben – nicht benötigte Alternativen treten zurück",
|
||||
implicitSizeTooltip:"Keine Größe angegeben – für die Kostenschätzung als M angenommen",
|
||||
@@ -599,6 +606,7 @@ const I18N = {
|
||||
privacy:"Privacy",
|
||||
legendTooltip:"Show/hide legend",
|
||||
ghostTooltip:"From size M upward, an item should be broken down further.",
|
||||
riskTooltip:"High risk – effort still unclear.",
|
||||
discardedTooltip:"Show/hide discarded nodes and their subtree",
|
||||
cheapTooltip:"Highlight the cheapest path – unneeded alternatives recede",
|
||||
implicitSizeTooltip:"No size given – assumed as M for the cost estimate",
|
||||
@@ -639,6 +647,7 @@ const I18N = {
|
||||
privacy:"Privacidad",
|
||||
legendTooltip:"Mostrar u ocultar la leyenda",
|
||||
ghostTooltip:"A partir de la talla M, un elemento debería desglosarse más.",
|
||||
riskTooltip:"Alto riesgo – esfuerzo aún incierto.",
|
||||
discardedTooltip:"Mostrar u ocultar los nodos descartados y su subárbol",
|
||||
cheapTooltip:"Resaltar la ruta más económica: las alternativas no necesarias se atenúan",
|
||||
implicitSizeTooltip:"Sin tamaño indicado: se asume M para el cálculo de costes",
|
||||
@@ -679,6 +688,7 @@ const I18N = {
|
||||
privacy:"Confidentialité",
|
||||
legendTooltip:"Afficher/masquer la légende",
|
||||
ghostTooltip:"À partir de la taille M, un élément devrait être décomposé davantage.",
|
||||
riskTooltip:"Risque élevé – effort encore incertain.",
|
||||
discardedTooltip:"Afficher/masquer les nœuds abandonnés et leur sous-arbre",
|
||||
cheapTooltip:"Mettre en évidence le chemin le moins coûteux – les alternatives inutiles s'estompent",
|
||||
implicitSizeTooltip:"Aucune taille indiquée – considérée comme M pour l'estimation des coûts",
|
||||
@@ -719,6 +729,7 @@ const I18N = {
|
||||
privacy:"Prywatność",
|
||||
legendTooltip:"Pokaż/ukryj legendę",
|
||||
ghostTooltip:"Od rozmiaru M element powinien być dalej podzielony.",
|
||||
riskTooltip:"Wysokie ryzyko – nakład jeszcze niejasny.",
|
||||
discardedTooltip:"Pokaż/ukryj odrzucone węzły wraz z poddrzewem",
|
||||
cheapTooltip:"Wyróżnij najtańszą ścieżkę – niepotrzebne alternatywy są przygaszone",
|
||||
implicitSizeTooltip:"Nie podano rozmiaru – przyjęto M do szacowania kosztów",
|
||||
@@ -759,6 +770,7 @@ const I18N = {
|
||||
privacy:"Конфиденциальность",
|
||||
legendTooltip:"Показать/скрыть легенду",
|
||||
ghostTooltip:"Начиная с размера M элемент следует далее декомпозировать.",
|
||||
riskTooltip:"Высокий риск – оценка ещё не ясна.",
|
||||
discardedTooltip:"Показать/скрыть отклонённые узлы вместе с поддеревом",
|
||||
cheapTooltip:"Выделить самый дешёвый путь — ненужные альтернативы приглушаются",
|
||||
implicitSizeTooltip:"Размер не указан — для оценки затрат принят как M",
|
||||
@@ -799,6 +811,7 @@ const I18N = {
|
||||
privacy:"गोपनीयता",
|
||||
legendTooltip:"लेजेंड दिखाएँ/छिपाएँ",
|
||||
ghostTooltip:"आकार M से ऊपर किसी तत्व को और अधिक उप-विभाजित करना चाहिए।",
|
||||
riskTooltip:"उच्च जोखिम – प्रयास अभी अस्पष्ट।",
|
||||
discardedTooltip:"अस्वीकृत नोड्स और उनके उप-वृक्ष दिखाएँ/छिपाएँ",
|
||||
cheapTooltip:"सबसे किफ़ायती पथ को उजागर करें – अनावश्यक विकल्प मंद हो जाते हैं",
|
||||
implicitSizeTooltip:"कोई आकार नहीं दिया गया – लागत अनुमान के लिए M माना गया",
|
||||
@@ -844,6 +857,7 @@ const I18N = {
|
||||
cheapTooltip:"突出显示成本最低的路径——不需要的备选项将淡化",
|
||||
implicitSizeTooltip:"未指定尺寸——成本估算时按 M 计",
|
||||
ghostTooltip:"从 M 号起,元素应进一步细分。",
|
||||
riskTooltip:"高风险 – 工作量尚不明确。",
|
||||
editorTitle:"结构(文本)", diagramTitle:"图表",
|
||||
copy:"复制", copyDone:"已复制 ✓", copyTooltip:"将文本复制到剪贴板",
|
||||
copyDiagramTooltip:"将图表作为 PNG 图片复制到剪贴板",
|
||||
@@ -884,6 +898,7 @@ const I18N = {
|
||||
cheapTooltip:"最も低コストの経路を強調 – 不要な選択肢は控えめに表示",
|
||||
implicitSizeTooltip:"サイズ未指定 – コスト見積もりのため M として扱う",
|
||||
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
|
||||
riskTooltip:"高リスク – 規模はまだ不明。",
|
||||
editorTitle:"構造(テキスト)", diagramTitle:"ダイアグラム",
|
||||
copy:"コピー", copyDone:"コピーしました ✓", copyTooltip:"テキストをクリップボードにコピー",
|
||||
copyDiagramTooltip:"ダイアグラムを PNG 画像としてクリップボードにコピー",
|
||||
@@ -1194,6 +1209,13 @@ mountBuildBadge();
|
||||
})();
|
||||
|
||||
/* ---------- Update-Detection (Client-seitig, einfach & zuverlässig) ---------- */
|
||||
/* Test-Hilfen (Debug-Panel + Reset-Button) nur AUSSERHALB des Prod-Builds:
|
||||
im Prod-Build (`build:prod` -> VITE_BUILD_BADGE==='none') eliminiert esbuild
|
||||
den Zweig als toten Code — analog zum Build-Badge (D16). Dev-Server (🔧) und
|
||||
Default-/Pages-Build (🚧) behalten sie. Der Update-Check + Banner bleibt in
|
||||
ALLEN Builds aktiv. */
|
||||
const isProdBuild = import.meta.env.VITE_BUILD_BADGE === 'none';
|
||||
|
||||
async function checkForUpdates(){
|
||||
try {
|
||||
/* Fetch HTML mit Cache-Busting via Timestamp */
|
||||
@@ -1255,8 +1277,9 @@ function logUpdate(msg){
|
||||
localStorage.setItem('werkbaum-update-log', log.join('\n'));
|
||||
}
|
||||
|
||||
/* Debug-Panel anzeigen (nur wenn localStorage Update-Log da ist) */
|
||||
/* Debug-Panel anzeigen (Test-Hilfe; im Prod-Build unterdrückt) */
|
||||
function showUpdateDebug(){
|
||||
if(isProdBuild) return;
|
||||
let panel = document.getElementById('updateDebugPanel');
|
||||
if(!panel){
|
||||
panel = document.createElement('div');
|
||||
@@ -1286,8 +1309,8 @@ function showUpdateDebug(){
|
||||
panel.textContent = log ? log.split('\n').slice(-6).join('\n') : 'Keine Einträge';
|
||||
}
|
||||
|
||||
/* Reset-Button im Header neben Fullscreen */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
/* Reset-Button im Header neben Fullscreen (Test-Hilfe; im Prod-Build weggelassen) */
|
||||
if(!isProdBuild) document.addEventListener('DOMContentLoaded', () => {
|
||||
const fsToggle = document.getElementById('fsToggle');
|
||||
if(!fsToggle) return;
|
||||
|
||||
|
||||
@@ -47,8 +47,14 @@ function nodeHtml(n, extra, opts){
|
||||
const sizeBadge = n.size
|
||||
? `<span class="size" aria-hidden="true">${n.size}</span>`
|
||||
: (cheapPath ? `<span class="size implicit" aria-hidden="true" title="${implicitTip}">M</span>` : '');
|
||||
/* High-Risk: Warndreieck (⚠, Textpräsentation via VS15) an der oberen linken
|
||||
Ecke. aria-hidden — die Information steckt bereits im Status des aria-label. */
|
||||
const riskMark = n.status && n.status.key === 'highrisk'
|
||||
? `<span class="risk" aria-hidden="true" title="${attr(t('riskTooltip'))}">⚠︎</span>`
|
||||
: '';
|
||||
const inner = esc(n.label) +
|
||||
(n.url ? '<span class="ext" aria-hidden="true">↗</span>' : '') +
|
||||
riskMark +
|
||||
sizeBadge +
|
||||
tagsHtml;
|
||||
const aria = ` aria-label="${attr(nodeAria(n, opts))}"`;
|
||||
|
||||
@@ -393,6 +393,15 @@
|
||||
border:1.5px solid var(--card);
|
||||
border-radius:999px;padding:1px 6px;line-height:1.4;
|
||||
}
|
||||
.risk{
|
||||
position:absolute;top:-10px;left:-10px;
|
||||
width:18px;height:18px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:.62rem;line-height:1;
|
||||
background:var(--card);color:#F97316;
|
||||
border:1.5px solid #F97316;border-radius:999px;
|
||||
box-shadow:0 0 0 1.5px var(--card);
|
||||
}
|
||||
.tags{
|
||||
position:absolute;right:-9px;bottom:-9px;
|
||||
display:flex;gap:3px;
|
||||
|
||||
Reference in New Issue
Block a user