← back to Ticket System
TK-11946: hold parked tickets out of the DEFAULT / board.html active queue
188325df35d6849680662babe459c437fcfd1caa · 2026-09-20 11:41:03 -0700 · Steve
The parked-lane peel-out only happened in page() at /kanban; the default /
route serves board.html (table view) which was parked-blind, so a parked
active ticket still showed in its open/doing/blocked section and counts —
defeating 'durably held out of the active queue' on the surface Steve defaults to.
withRanking now stamps t.parked onto every /api/tickets row; board.html holds
parked tickets out of active-status sections and relocates them to a 'parked'
group when grouping by status (mirrors the /kanban lane). Verified headless:
parked TK-16 absent from the doing section, control non-parked ticket unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 188325df35d6849680662babe459c437fcfd1caa
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Sep 20 11:41:03 2026 -0700
TK-11946: hold parked tickets out of the DEFAULT / board.html active queue
The parked-lane peel-out only happened in page() at /kanban; the default /
route serves board.html (table view) which was parked-blind, so a parked
active ticket still showed in its open/doing/blocked section and counts —
defeating 'durably held out of the active queue' on the surface Steve defaults to.
withRanking now stamps t.parked onto every /api/tickets row; board.html holds
parked tickets out of active-status sections and relocates them to a 'parked'
group when grouping by status (mirrors the /kanban lane). Verified headless:
parked TK-16 absent from the doing section, control non-parked ticket unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
board.html | 6 +++---
server.js | 6 +++++-
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/board.html b/board.html
index 2c849b80..1c01a272 100644
--- a/board.html
+++ b/board.html
@@ -292,7 +292,7 @@ const DW_KW=/wallcover|wallpaper/i; // DW wal
const DW_VENDOR=/(^|[^a-z])(sanderson|stroheim|muralsource|kravet|greenland|malibu|gracie|astek|fabricut|groundworks|rebel-walls|thibaut|schumacher|koroseal|scalamandre|maya-?romanoff|winfield|phillip-?jeffries|brewster|wallquest|carnegie|designtex|momentum|versa|elitis|fromental|fentucci|hollywood|novasuede|philipperomano|mdc)($|[^a-z])/i;
const DW_EXTRA=new Set(['norma','greenland-onboard','sample-followup','sample-followup-sweep','interiordesignershowroom','marketing-command-center','all-designerwallcoverings','consulting-designerwallcoverings-com','mfr-review-viewer','mfr-review-viewer-corruption','designersalesreps','vendor-command-center','discontinued-agent']);
function isDW(t){const p=String(t.project||'').toLowerCase().trim();if(!p||DW_DENY.has(p))return false;return DW_EXTRA.has(p)||DW_RE.test(p)||DW_VENDOR.test(p)||DW_KW.test(p);}
-function match(t){if(VIEW!=='all'&&(t.kind||'task')!==VIEW)return false;if(['open','blocked','doing'].includes(SECTION)&&t.status!==SECTION)return false;if(SECTION==='nondw'&&isDW(t))return false;if(!q)return true;
+function match(t){if(VIEW!=='all'&&(t.kind||'task')!==VIEW)return false;if(['open','blocked','doing'].includes(SECTION)&&t.status!==SECTION)return false;if(['open','blocked','doing'].includes(SECTION)&&t.parked)return false;/*TK-11946: parked held out of the active queue (mirrors /kanban parked lane)*/if(SECTION==='nondw'&&isDW(t))return false;if(!q)return true;
// search the FULL thread — every comment (note/win/challenge/cody incl.) + every action — not just lastText
const thread=[...(t.comments||[]).map(c=>c.text),...(t.actions||[]).map(a=>a.text)].join(' ');
const hay=(t.id+' '+t.title+' '+(t.project||'')+' '+(t.assignee||'')+' '+t.status+' '+(t.kind||'task')+' '+JSON.stringify(t.schedule||{})+' '+thread).toLowerCase();
@@ -328,9 +328,9 @@ function render(){
for(const t of rows)rowTr(t);
}else{
const groups=new Map();
- for(const t of rows){const gv=GROUP==='status'?t.status:GROUP==='blocker'?(t.blocker?.type||''):GROUP==='project'?(t.project||''):(t.assignee||'');(groups.get(gv)||groups.set(gv,[]).get(gv)).push(t);}
+ for(const t of rows){const gv=GROUP==='status'?((t.parked&&['open','doing','blocked'].includes(t.status))?'parked':t.status)/*TK-11946*/:GROUP==='blocker'?(t.blocker?.type||''):GROUP==='project'?(t.project||''):(t.assignee||'');(groups.get(gv)||groups.set(gv,[]).get(gv)).push(t);}
// order groups: for status use canonical order, else alpha
- const order=GROUP==='status'?['open','doing','blocked','done','stopped']:[...groups.keys()].sort((a,b)=>String(a).localeCompare(String(b)));
+ const order=GROUP==='status'?['open','doing','blocked','done','stopped','parked']:[...groups.keys()].sort((a,b)=>String(a).localeCompare(String(b)));
for(const gv of order){const list=groups.get(gv);if(!list)continue;
const gkey=GROUP+':'+gv; const collapsed=COLLAPSED.has(gkey);
const allSel=list.every(t=>SEL.has(t.id))&&list.length>0;
diff --git a/server.js b/server.js
index 8a81aebe..fa29ade8 100644
--- a/server.js
+++ b/server.js
@@ -458,7 +458,11 @@ function withRanking(list) {
const parked = parkedTicketIds();
const scored = [];
for (const t of list) {
- if ((t.kind || 'task') === 'task' && RANKABLE.has(t.status) && !parked.has(shortTk(t.id))) {
+ // TK-11946: stamp the durable parked flag onto every ticket so the DEFAULT `/`
+ // table view (board.html, which reads /api/tickets) can hold a parked ticket OUT
+ // of the active queue — the /kanban parked-lane peel-out was the only surface honoring it.
+ t.parked = parked.has(shortTk(t.id));
+ if ((t.kind || 'task') === 'task' && RANKABLE.has(t.status) && !t.parked) {
const s = scoreTicket(t);
t.priority = s.priority; t.tier = s.tier;
t.ratings = { value: s.value, urgency: s.urgency, ease: s.ease, safety: s.safety };
← e8b78db9 ticket board: PARKED lane + reaper skips parked tickets (dur
·
back to Ticket System
·
TK-11951: needs-Steve tab backlog triage/fire-list (6 tabs a 3ca99fae →