spectoflow 0.23.4 → 0.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectoflow",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.5",
|
|
4
4
|
"description": "Agent-agnostic spec-driven development framework + real-time local control plane. Markdown artifacts, intent router, workflow-by-scope.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spec-driven-development",
|
|
@@ -363,17 +363,26 @@ function renderOverview(){
|
|
|
363
363
|
topRow.append(ocard(t('chart.scopeVsDelivered'), area));
|
|
364
364
|
box.append(topRow);
|
|
365
365
|
|
|
366
|
-
// Workflow-at-a-glance strip (reuses the wf-arrow flow animation)
|
|
366
|
+
// Workflow-at-a-glance strip (reuses the wf-arrow flow animation) — clicking a step opens the same
|
|
367
|
+
// enable/disable popover as the dedicated Workflow tab (openWfPop/renderWfPop), so a step can be
|
|
368
|
+
// toggled right from the Board without switching tabs.
|
|
367
369
|
const strip=el('div','wf-strip');
|
|
368
370
|
const steps=P.workflow||[];
|
|
369
371
|
steps.forEach((st,i)=>{
|
|
370
|
-
const node=el('div','wf-mini'+(st.enabled?'':' off'));
|
|
372
|
+
const node=el('div','wf-mini'+(st.enabled?'':' off')+(st.name===wfPopStep?' is-selected':''));
|
|
373
|
+
node.dataset.name=st.name;
|
|
374
|
+
node.tabIndex=0; node.setAttribute('role','button'); node.setAttribute('aria-expanded',String(st.name===wfPopStep));
|
|
375
|
+
node.title=st.name;
|
|
371
376
|
node.append(el('span','dot')); node.append(el('span','nm',st.name));
|
|
377
|
+
const sel=(e)=>{ e.stopPropagation(); if(wfPopStep===st.name) closeWfPop(); else openWfPop(st.name); };
|
|
378
|
+
node.addEventListener('click',sel);
|
|
379
|
+
node.addEventListener('keydown',e=>{ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); sel(e); } });
|
|
372
380
|
strip.append(node);
|
|
373
381
|
if(i<steps.length-1){ const a=el('div','wf-arrow'+(st.enabled&&steps[i+1].enabled?'':' off')); strip.append(a); }
|
|
374
382
|
});
|
|
375
383
|
if(!steps.length) strip.append(el('div','empty',t('board.noWorkflow')));
|
|
376
384
|
box.append(ocard(t('board.workflowGlance'), strip));
|
|
385
|
+
if(wfPopStep) renderWfPop(); // re-anchor an open popover after a live re-render of this strip too
|
|
377
386
|
|
|
378
387
|
// Per-phase progress bars — only phases that actually hold tasks (headings with no checkbox tasks
|
|
379
388
|
// are noise, not phases), and cap the list height with an internal scroll so a big project with
|
|
@@ -675,16 +684,27 @@ function wfPopFill(pop, s, idx){
|
|
|
675
684
|
const actions=el('div','wf-pop-actions'); actions.append(btn); pop.append(actions);
|
|
676
685
|
}
|
|
677
686
|
function openWfPop(name){ wfPopStep=name; renderWfPop(); }
|
|
678
|
-
function closeWfPop(){ wfPopStep=null; const p=$('#wfPop'); if(p) p.hidden=true; $$('#wfDiagram .wf-step2.is-selected').forEach(x=>x.classList.remove('is-selected')); }
|
|
687
|
+
function closeWfPop(){ wfPopStep=null; const p=$('#wfPop'); if(p) p.hidden=true; $$('#wfDiagram .wf-step2.is-selected,.wf-mini.is-selected').forEach(x=>x.classList.remove('is-selected')); }
|
|
688
|
+
// The workflow popover has two possible triggers now: the dedicated Workflow tab's own pipeline
|
|
689
|
+
// (#wfDiagram .wf-step2), and the Board's "at a glance" strip (.wf-mini). Both stay mounted in the
|
|
690
|
+
// DOM at all times (every .panel renders every tick; only CSS hides the inactive ones), so anchoring
|
|
691
|
+
// always prefers whichever one sits on the currently VISIBLE panel — a hidden panel's element has a
|
|
692
|
+
// zero-size getBoundingClientRect() and would position the popover at the top-left corner.
|
|
693
|
+
function findWfAnchor(name){
|
|
694
|
+
const boardActive=document.querySelector('.panel[data-panel="board"].is-active');
|
|
695
|
+
if(boardActive){ const m=boardActive.querySelector('.wf-mini[data-name="'+CSS.escape(name)+'"]'); if(m) return m; }
|
|
696
|
+
const idx=(P.workflow||[]).findIndex(s=>s.name===name);
|
|
697
|
+
return $('#wfDiagram .wf-step2[data-idx="'+idx+'"]');
|
|
698
|
+
}
|
|
679
699
|
function renderWfPop(){
|
|
680
700
|
const p=$('#wfPop'); if(!p) return;
|
|
681
701
|
const steps=P.workflow||[]; const idx=steps.findIndex(s=>s.name===wfPopStep);
|
|
682
702
|
if(idx<0){ closeWfPop(); return; }
|
|
683
|
-
const anchor
|
|
703
|
+
const anchor=findWfAnchor(wfPopStep);
|
|
684
704
|
if(!anchor){ p.hidden=true; return; }
|
|
685
|
-
$$('#wfDiagram .wf-step2.is-selected').forEach(x=>x.classList.remove('is-selected')); anchor.classList.add('is-selected');
|
|
705
|
+
$$('#wfDiagram .wf-step2.is-selected,.wf-mini.is-selected').forEach(x=>x.classList.remove('is-selected')); anchor.classList.add('is-selected');
|
|
686
706
|
wfPopFill(p, steps[idx], idx);
|
|
687
|
-
positionWfPop(anchor.querySelector('.wf-circle')||anchor, p);
|
|
707
|
+
positionWfPop(anchor.classList.contains('wf-step2')?(anchor.querySelector('.wf-circle')||anchor):anchor, p);
|
|
688
708
|
}
|
|
689
709
|
function positionWfPop(anchorEl, pop){
|
|
690
710
|
const r=anchorEl.getBoundingClientRect();
|
|
@@ -1070,6 +1090,7 @@ function tabFromPath(){
|
|
|
1070
1090
|
}
|
|
1071
1091
|
function taskFromPath(){ const s=pathSegments(); return (ROUTES.includes(s[0])&&s[1])?decodeURIComponent(s[1]):null; }
|
|
1072
1092
|
function navigateTab(tabId,push){
|
|
1093
|
+
closeWfPop(); // an open popover is anchored to whichever panel is currently active — never carry it across
|
|
1073
1094
|
activeTab=tabId; try{ localStorage.setItem('spf-tab',tabId); }catch{}
|
|
1074
1095
|
if(push!==false){
|
|
1075
1096
|
const isCustom=tabId.indexOf('custom:')===0;
|
|
@@ -1711,7 +1732,7 @@ document.addEventListener('click',e=>{ if(!document.body.classList.contains('nav
|
|
|
1711
1732
|
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeNav(); });
|
|
1712
1733
|
// workflow step popover — close on outside click / Esc / resize. (No scroll-to-close: a capture
|
|
1713
1734
|
// scroll listener also fires on the popover's own internal scroll, which would slam it shut.)
|
|
1714
|
-
document.addEventListener('click',e=>{ if(wfPopStep && !e.target.closest('#wfPop') && !e.target.closest('.wf-step2')) closeWfPop(); });
|
|
1735
|
+
document.addEventListener('click',e=>{ if(wfPopStep && !e.target.closest('#wfPop') && !e.target.closest('.wf-step2') && !e.target.closest('.wf-mini')) closeWfPop(); });
|
|
1715
1736
|
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeWfPop(); });
|
|
1716
1737
|
window.addEventListener('resize',()=>{ if(wfPopStep) closeWfPop(); });
|
|
1717
1738
|
// keep the URL and the path in sync when the user uses the browser back/forward buttons
|
|
@@ -142,7 +142,12 @@ body { background:var(--bg); color:var(--ink); font-family:var(--sans); font-siz
|
|
|
142
142
|
.legend-count { font-family:var(--mono); color:var(--ink); font-weight:600; }
|
|
143
143
|
|
|
144
144
|
.wf-strip { display:flex; flex-wrap:wrap; align-items:center; gap:0; }
|
|
145
|
-
|
|
145
|
+
/* Clickable — opens the same enable/disable popover as the dedicated Workflow tab (see
|
|
146
|
+
findWfAnchor/openWfPop in app.js), so a step can be toggled right from this Board glance strip. */
|
|
147
|
+
.wf-mini { display:flex; align-items:center; gap:6px; padding:6px 11px; border:1px solid var(--line); border-radius:999px; background:var(--surface-2); font-size:11.5px; font-weight:600; white-space:nowrap; cursor:pointer; transition:border-color .15s,transform .15s,box-shadow .15s; }
|
|
148
|
+
.wf-mini:hover { border-color:var(--signal); transform:translateY(-1px); }
|
|
149
|
+
.wf-mini:focus-visible { outline:2px solid var(--signal); outline-offset:1px; }
|
|
150
|
+
.wf-mini.is-selected { border-color:var(--signal); border-style:solid; box-shadow:0 0 0 3px color-mix(in srgb,var(--signal) 20%,transparent); }
|
|
146
151
|
.wf-mini .dot { width:6px; height:6px; border-radius:50%; background:var(--s-done); }
|
|
147
152
|
.wf-mini.off { opacity:.42; border-style:dashed; }
|
|
148
153
|
.wf-mini.off .dot { background:var(--faint); }
|