@@ -575,6 +575,11 @@ function msToDateTimeLocalStr(ms) {
575575 const mm = String ( d . getMinutes ( ) ) . padStart ( 2 , "0" ) ;
576576 return `${ y } -${ m } -${ day } T${ hh } :${ mm } ` ;
577577}
578+ const DEFAULT_WORKFLOW_LANE_COLOR = "#64748b" ;
579+ function normalizeWorkflowLaneColorForInput ( color ) {
580+ const s = color ?. trim ( ) ;
581+ return s && / ^ # [ 0 - 9 a - f A - F ] { 6 } $ / . test ( s ) ? s : DEFAULT_WORKFLOW_LANE_COLOR ;
582+ }
578583function renderWorkflowTabContent ( ) {
579584 const board = getBoard ( ) ;
580585 const workflow = board ?. columnOrder ?? [ ] ;
@@ -588,7 +593,7 @@ function renderWorkflowTabContent() {
588593 return `
589594 <div class="settings-section">
590595 <div class="settings-section__title">Workflow</div>
591- <div class="settings-section__description muted">Rename lane labels or add a non-done lane inserted immediately before the done lane (whatever its label). Keys stay immutable.</div>
596+ <div class="settings-section__description muted">Rename lane labels and colors, or add a non-done lane inserted immediately before the done lane (whatever its label). Keys stay immutable.</div>
592597 <div class="settings-workflow-create" style="display:flex; gap:12px; align-items:flex-end; margin-bottom:16px;">
593598 <label class="field" style="flex:1; min-width:0; margin:0;">
594599 <div class="field__label">New lane name</div>
@@ -614,6 +619,14 @@ function renderWorkflowTabContent() {
614619 aria-label="Lane label for ${ escapeHTML ( lane . key ) } "
615620 style="flex:1; min-width:0;"
616621 />
622+ <input
623+ type="color"
624+ class="settings-color-picker"
625+ data-workflow-color="${ escapeHTML ( lane . key ) } "
626+ value="${ escapeHTML ( normalizeWorkflowLaneColorForInput ( lane . color ) ) } "
627+ aria-label="Lane color for ${ escapeHTML ( lane . key ) } "
628+ title="Lane color"
629+ />
617630 <button class="btn btn--ghost btn--small" type="button" data-workflow-save="${ escapeHTML ( lane . key ) } ">Save</button>
618631 ${ lane . isDone ? `<button class="btn btn--ghost btn--small" type="button" disabled aria-disabled="true" title="Done lane cannot be deleted">Delete</button>` : `<button class="btn btn--danger btn--small" type="button" data-workflow-delete="${ escapeHTML ( lane . key ) } " ${ canDeleteAnyLane ? "" : `disabled aria-disabled="true" title="Workflow must keep at least 2 lanes"` } >Delete</button>` }
619632 </div>
@@ -647,32 +660,35 @@ async function addWorkflowLane(name) {
647660 showToast ( err . message || "Failed to add lane" ) ;
648661 }
649662}
650- async function renameWorkflowLaneLabel ( key , name ) {
663+ async function updateWorkflowLane ( key , payload ) {
651664 const slug = getSlug ( ) ;
652665 if ( ! slug ) {
653666 showToast ( "No project available" ) ;
654667 return ;
655668 }
656- const trimmed = name . trim ( ) ;
669+ const trimmed = payload . name . trim ( ) ;
657670 if ( ! trimmed ) {
658671 showToast ( "Lane name is required" ) ;
659672 return ;
660673 }
661- const currentName = getBoard ( ) ?. columnOrder ?. find ( ( lane ) => lane . key === key ) ?. name ?. trim ( ) ;
662- if ( currentName === trimmed )
674+ const color = payload . color . trim ( ) ;
675+ const lane = getBoard ( ) ?. columnOrder ?. find ( ( l ) => l . key === key ) ;
676+ const prevColor = ( lane ?. color ?? DEFAULT_WORKFLOW_LANE_COLOR ) . toLowerCase ( ) ;
677+ if ( trimmed === lane ?. name ?. trim ( ) && color . toLowerCase ( ) === prevColor ) {
663678 return ;
679+ }
664680 try {
665681 recordLocalMutation ( ) ;
666682 await apiFetch ( `/api/board/${ slug } /workflow/${ encodeURIComponent ( key ) } ` , {
667683 method : "PATCH" ,
668- body : JSON . stringify ( { name : trimmed } ) ,
684+ body : JSON . stringify ( { name : trimmed , color } ) ,
669685 } ) ;
670686 await invalidateBoard ( slug , getTag ( ) , getSearch ( ) , getSprintIdFromUrl ( ) ) ;
671687 await renderSettingsModal ( ) ;
672- showToast ( "Lane label updated" ) ;
688+ showToast ( "Lane updated" ) ;
673689 }
674690 catch ( err ) {
675- showToast ( err . message || "Failed to update lane label " ) ;
691+ showToast ( err . message || "Failed to update lane" ) ;
676692 }
677693}
678694async function deleteWorkflowLane ( key ) {
@@ -1341,18 +1357,20 @@ export async function renderSettingsModal(options) {
13411357 addLane ( ) ;
13421358 } , { signal } ) ;
13431359 }
1344- const bindRename = ( key ) => {
1345- const input = document . querySelector ( `[data-workflow-name="${ key } "]` ) ;
1346- if ( ! input )
1360+ const saveWorkflowLane = ( key ) => {
1361+ const row = document . querySelector ( `[data-workflow-key="${ key } "]` ) ;
1362+ const nameInput = row ?. querySelector ( "[data-workflow-name]" ) ;
1363+ const colorInput = row ?. querySelector ( "[data-workflow-color]" ) ;
1364+ if ( ! nameInput || ! colorInput )
13471365 return ;
1348- renameWorkflowLaneLabel ( key , input . value ) ;
1366+ updateWorkflowLane ( key , { name : nameInput . value , color : colorInput . value } ) ;
13491367 } ;
13501368 document . querySelectorAll ( "[data-workflow-save]" ) . forEach ( ( btn ) => {
13511369 btn . addEventListener ( "click" , ( ) => {
13521370 const key = btn . getAttribute ( "data-workflow-save" ) ;
13531371 if ( ! key )
13541372 return ;
1355- bindRename ( key ) ;
1373+ saveWorkflowLane ( key ) ;
13561374 } , { signal } ) ;
13571375 } ) ;
13581376 document . querySelectorAll ( "[data-workflow-name]" ) . forEach ( ( inputEl ) => {
@@ -1363,7 +1381,18 @@ export async function renderSettingsModal(options) {
13631381 const key = inputEl . getAttribute ( "data-workflow-name" ) ;
13641382 if ( ! key )
13651383 return ;
1366- bindRename ( key ) ;
1384+ saveWorkflowLane ( key ) ;
1385+ } , { signal } ) ;
1386+ } ) ;
1387+ document . querySelectorAll ( "[data-workflow-color]" ) . forEach ( ( colorEl ) => {
1388+ colorEl . addEventListener ( "keydown" , ( e ) => {
1389+ if ( e . key !== "Enter" )
1390+ return ;
1391+ e . preventDefault ( ) ;
1392+ const key = colorEl . getAttribute ( "data-workflow-color" ) ;
1393+ if ( ! key )
1394+ return ;
1395+ saveWorkflowLane ( key ) ;
13671396 } , { signal } ) ;
13681397 } ) ;
13691398 document . querySelectorAll ( "[data-workflow-delete]" ) . forEach ( ( btn ) => {
0 commit comments