@stephen1620/dsh-tomato-board 0.1.1 → 0.3.0

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/lib/client.js CHANGED
@@ -18,20 +18,25 @@ window.__ModuleLoader__.load({
18
18
  }
19
19
  //#endregion
20
20
  //#region \0dsh-css:/Users/gengfeng/deepseek-harness/packages/client/tomato-board/src/client/tomato-board.module.css.mjs
21
- const css = ".Y6kksa_sidebarAction{width:100%;min-height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;white-space:nowrap;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);background:0 0;border:0;border-radius:10px;align-items:center;gap:9px;margin:0;padding:0 10px;display:flex}.Y6kksa_sidebarAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_tomatoIcon{width:18px;height:18px;color:var(--dsw-alias-label-primary-inverted);background:var(--dsw-alias-state-error-primary);border-radius:6px;flex:none;place-items:center;font-size:11px;font-weight:750;display:inline-grid}.Y6kksa_transitionTrigger{align-items:center;gap:6px;display:inline-flex}.Y6kksa_transitionCaption{color:var(--dsw-alias-label-secondary);font-size:12px}.Y6kksa_transitionTrigger strong{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:650}.Y6kksa_transitionDivider{background:var(--dsw-alias-border-l2);width:1px;height:14px;margin:0 2px}.Y6kksa_transitionAction{color:var(--dsw-alias-state-business-primary);font-size:12px;font-weight:650}.Y6kksa_transitionChevron{color:var(--dsw-alias-state-business-primary);transition:transform var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.Y6kksa_transitionTrigger[aria-expanded=true] .Y6kksa_transitionChevron{transform:rotate(180deg)}.Y6kksa_workbench{min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font-family:var(--dsw-font-family);flex-direction:column;flex:1;display:flex;position:relative}.Y6kksa_createDialog{width:min(480px,100%)}.Y6kksa_dialogBody{flex-direction:column;gap:12px;display:flex}.Y6kksa_dialogBody>span{color:var(--dsw-alias-state-error-primary);font-size:12px;font-weight:700}.Y6kksa_dialogBody code{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;font-size:13px}.Y6kksa_workspaceTrigger{justify-content:space-between;width:100%;display:flex}.Y6kksa_header{border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 72%, transparent);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 72%, transparent);backdrop-filter:blur(16px);justify-content:space-between;align-items:center;min-height:52px;padding:8px 16px;display:flex}.Y6kksa_header h1{margin:0;font-size:16px;line-height:22px}.Y6kksa_header p{color:var(--dsw-alias-label-secondary);margin:1px 0 0;font-size:12px;line-height:16px}.Y6kksa_actions{align-items:center;gap:8px;display:flex}.Y6kksa_actions button{border:1px solid var(--dsw-alias-border-l2);min-height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-elevated-fill);cursor:pointer;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);border-radius:10px;padding:0 14px}.Y6kksa_actions button:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-button-floating-hover)}.Y6kksa_actions button:disabled{cursor:wait;opacity:.55}.Y6kksa_actions .Y6kksa_headerIconButton{border-radius:8px;width:34px;min-width:34px;height:34px;min-height:34px;padding:0}.Y6kksa_searchField{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:6px;width:220px;min-height:34px;padding:0 8px;display:flex}.Y6kksa_searchField input{min-width:0;color:inherit;font:inherit;background:0 0;border:0;outline:0;flex:1}.Y6kksa_searchField button{border:0;min-height:24px;padding:0 5px}.Y6kksa_filterMenu{position:relative}.Y6kksa_filterMenu summary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);cursor:pointer;border-radius:10px;place-items:center;width:34px;height:34px;list-style:none;display:grid;position:relative}.Y6kksa_filterMenu summary::-webkit-details-marker{display:none}.Y6kksa_filterMenu summary i{background:var(--dsw-alias-state-error-primary);border-radius:50%;width:5px;height:5px;position:absolute;top:5px;right:5px}.Y6kksa_filterPopover{z-index:10;border:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 94%, transparent);width:min(760px,100vw - 320px);box-shadow:var(--dsw-shadow-lv3);backdrop-filter:blur(18px);border-radius:16px;gap:12px;padding:14px;display:grid;position:absolute;top:calc(100% + 8px);right:0}.Y6kksa_filterRow{align-items:flex-start;gap:12px;display:flex}.Y6kksa_filterRow>span{width:36px;color:var(--dsw-alias-label-secondary);padding-top:7px;font-size:13px}.Y6kksa_filterRow>div{flex-wrap:wrap;flex:1;gap:7px;display:flex}.Y6kksa_filterRow button{min-height:30px;color:var(--dsw-alias-label-secondary);padding:3px 11px}.Y6kksa_filterRow .Y6kksa_selectedFilter{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent)}.Y6kksa_error{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-secondary);border-radius:12px;margin:16px 24px 0;padding:12px 14px}.Y6kksa_notice{color:var(--dsw-alias-label-secondary);margin:0;padding:8px 20px;font-size:11px}.Y6kksa_board{flex:1;align-items:flex-start;gap:12px;padding:8px 12px;display:flex;overflow:auto}.Y6kksa_lane{border:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-bg-module-platform) 86%, transparent);border-radius:16px;flex-direction:column;width:300px;min-width:300px;max-height:100%;display:flex}.Y6kksa_laneHeader{justify-content:space-between;align-items:center;padding:12px 14px 10px;display:flex}.Y6kksa_laneHeader h2{margin:0;font-size:14px}.Y6kksa_laneHeader span{text-align:center;min-width:22px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-overlay);border-radius:999px;padding:2px 6px;font-size:12px}.Y6kksa_cards{padding:0 8px 8px;overflow-y:auto}.Y6kksa_card{border:1px solid var(--dsw-alias-border-l2);min-width:0;max-width:100%;color:inherit;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);box-shadow:var(--dsw-shadow-lv1);text-align:left;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out), box-shadow var(--ds-transition-duration-fast) var(--ds-ease-in-out);cursor:pointer;border-radius:14px;flex-direction:column;gap:7px;margin-top:8px;padding:12px;text-decoration:none;display:flex;overflow:hidden}.Y6kksa_card:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-button-floating-hover);box-shadow:var(--dsw-shadow-lv2)}.Y6kksa_cardMuted{opacity:.46;filter:saturate(.2);box-shadow:none}.Y6kksa_cardMuted:hover{opacity:.62}.Y6kksa_card:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.Y6kksa_card strong,.Y6kksa_key,.Y6kksa_meta{overflow-wrap:anywhere;word-break:normal;min-width:0}.Y6kksa_cardTopline{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.Y6kksa_cardButtons{align-items:center;gap:2px;display:inline-flex}.Y6kksa_muteButton,.Y6kksa_tomatoLink{border-radius:8px;flex:none;place-items:center;width:28px;min-width:28px;height:28px;min-height:28px;padding:0;line-height:1;display:inline-grid}.Y6kksa_muteButton{color:var(--dsw-alias-label-tertiary);font-size:9px}.Y6kksa_muteButton[aria-pressed=true]{color:var(--dsw-alias-label-primary)}.Y6kksa_card strong{font-size:14px;font-weight:600;line-height:1.45}.Y6kksa_key{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:650}.Y6kksa_meta{color:var(--dsw-alias-label-tertiary);font-size:12px}.Y6kksa_empty{color:var(--dsw-alias-label-tertiary);margin:auto}@media (width<=820px){.Y6kksa_header{align-items:flex-start;gap:10px}.Y6kksa_actions{flex-wrap:wrap;justify-content:flex-end}.Y6kksa_searchField{width:170px}}";
22
- const tagId = "@stephen1620/dsh-tomato-board/tomato-board.module.css";
23
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
21
+ const css$2 = ".Y6kksa_sidebarAction{width:100%;min-height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;white-space:nowrap;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);background:0 0;border:0;border-radius:10px;align-items:center;gap:9px;margin:0;padding:0 10px;display:flex}.Y6kksa_sidebarAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_topbarAction{border:1px solid var(--dsw-alias-border-l2);min-height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);cursor:pointer;font:inherit;white-space:nowrap;border-radius:8px;align-items:center;gap:7px;padding:4px 9px;font-size:12px;font-weight:600;display:inline-flex}.Y6kksa_topbarAction:hover{border-color:color-mix(in srgb, var(--tomato-accent,var(--dsw-alias-state-error-primary)) 45%, var(--dsw-alias-border-l2));background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_topbarAction:focus-visible{outline:2px solid var(--tomato-accent,var(--dsw-alias-state-error-primary));outline-offset:2px}.Y6kksa_topbarAction .Y6kksa_tomatoIcon{border-radius:5px;width:16px;height:16px;font-size:10px}.Y6kksa_tomatoIcon{width:18px;height:18px;color:var(--dsw-alias-label-primary-inverted);background:var(--tomato-accent,var(--dsw-alias-state-error-primary));border-radius:6px;flex:none;place-items:center;font-size:11px;font-weight:750;display:inline-grid}.Y6kksa_transitionTrigger{align-items:center;gap:6px;display:inline-flex}.Y6kksa_transitionCaption{color:var(--dsw-alias-label-secondary);font-size:12px}.Y6kksa_transitionTrigger strong{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:650}.Y6kksa_transitionDivider{background:var(--dsw-alias-border-l2);width:1px;height:14px;margin:0 2px}.Y6kksa_transitionAction{color:var(--tomato-accent,var(--dsw-alias-state-business-primary));font-size:12px;font-weight:650}.Y6kksa_transitionChevron{color:var(--tomato-accent,var(--dsw-alias-state-business-primary));transition:transform var(--ds-transition-duration-fast) var(--ds-ease-in-out)}.Y6kksa_transitionTrigger[aria-expanded=true] .Y6kksa_transitionChevron{transform:rotate(180deg)}.Y6kksa_workbench{--tomato-accent:var(--dsw-alias-state-business-primary,#3478c8);--tomato-accent-soft:color-mix(in srgb, var(--tomato-accent) 12%, transparent);--tomato-accent-border:color-mix(in srgb, var(--tomato-accent) 48%, var(--dsw-alias-border-l2));min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font-family:var(--dsw-font-family);flex-direction:column;flex:1;display:flex;position:relative}.Y6kksa_createDialog{width:min(480px,100%)}.Y6kksa_dialogBody{flex-direction:column;gap:12px;display:flex}.Y6kksa_dialogBody>span{color:var(--tomato-accent,var(--dsw-alias-state-error-primary));font-size:12px;font-weight:700}.Y6kksa_dialogBody code{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;font-size:13px}.Y6kksa_workspaceTrigger{justify-content:space-between;width:100%;display:flex}.Y6kksa_header{z-index:20;border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.Y6kksa_header:after{content:\"\";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.Y6kksa_titleRow{align-items:center;gap:10px;min-height:32px;display:flex}.Y6kksa_header h1{text-overflow:ellipsis;white-space:nowrap;border-radius:12px;flex:1;min-width:0;margin:0;padding:4px 8px;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.Y6kksa_pageTabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.Y6kksa_pageTabs button{color:var(--dsw-alias-label-tertiary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.Y6kksa_pageTabs button:after{content:\"\";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.Y6kksa_pageTabs button:hover,.Y6kksa_pageTabs button[aria-current=page]{color:var(--dsw-alias-state-business-primary)}.Y6kksa_pageTabs button[aria-current=page]:after{background:var(--dsw-alias-state-business-primary)}.Y6kksa_pageTabs button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:3px}.Y6kksa_actions{z-index:1;align-items:center;gap:8px;min-height:32px;display:flex;position:relative}.Y6kksa_actions .Y6kksa_headerIconButton{width:28px;min-width:28px;height:28px;padding:0}.Y6kksa_searchField{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:6px;width:220px;min-height:28px;padding:0 8px;display:flex}.Y6kksa_searchField input{min-width:0;color:inherit;font:inherit;background:0 0;border:0;outline:0;flex:1}.Y6kksa_searchField button{border:0;min-height:24px;padding:0 5px}.Y6kksa_filterMenu{position:relative}.Y6kksa_filterMenu summary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);cursor:pointer;border-radius:10px;place-items:center;width:28px;height:28px;list-style:none;display:grid;position:relative}.Y6kksa_filterMenu summary::-webkit-details-marker{display:none}.Y6kksa_filterMenu summary i{background:var(--tomato-accent,var(--dsw-alias-state-error-primary));border-radius:50%;width:5px;height:5px;position:absolute;top:5px;right:5px}.Y6kksa_filterPopover{z-index:30;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);width:min(500px,100vw - 320px);box-shadow:var(--dsw-shadow-lv3);border-radius:16px;gap:10px;padding:12px;display:grid;position:absolute;top:calc(100% + 8px);right:0}.Y6kksa_assigneeFilter{align-items:flex-start;gap:12px;display:flex}.Y6kksa_assigneeFilter>span{width:48px;color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:0 0 48px;padding-top:8px;font-size:13px}.Y6kksa_assigneePicker{min-width:260px;position:relative}.Y6kksa_assigneeFilter .Y6kksa_assigneeTrigger{border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 82%, transparent);width:100%;height:30px;color:var(--dsw-alias-label-secondary);background:color-mix(in srgb, var(--dsw-alias-bg-overlay) 46%, transparent);border-radius:999px;justify-content:space-between;align-items:center;gap:12px;padding:0 8px 0 12px;font-size:12px;display:flex}.Y6kksa_assigneeFilter .Y6kksa_assigneeTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_assigneeTrigger>span{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.Y6kksa_assigneeDropdown{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:320px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;padding:8px;position:absolute;top:calc(100% + 5px);left:0}.Y6kksa_assigneeDropdown>input{border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);font:inherit;border-radius:7px;outline:0;padding:0 9px}.Y6kksa_assigneeDropdown>input:focus{border-color:var(--tomato-accent,var(--dsw-alias-state-business-primary))}.Y6kksa_assigneeDropdown>[role=listbox]{max-height:260px;margin-top:7px;overflow:auto}.Y6kksa_assigneeDropdown [role=option]{width:100%;min-height:38px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:7px;flex-direction:column;justify-content:center;align-items:flex-start;gap:2px;padding:6px 9px;display:flex}.Y6kksa_assigneeDropdown [role=option]:hover,.Y6kksa_assigneeDropdown [role=option][aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_assigneeDropdown strong{font-size:12px;font-weight:550}.Y6kksa_assigneeDropdown small{color:var(--dsw-alias-label-secondary);font-size:10px}.Y6kksa_assigneeDropdown p{color:var(--dsw-alias-label-secondary);text-align:center;margin:0;padding:18px 8px;font-size:11px}.Y6kksa_filterRow{align-items:flex-start;gap:12px;display:flex}.Y6kksa_filterRow>span{width:48px;color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:0 0 48px;padding-top:7px;font-size:13px}.Y6kksa_filterRow>div{flex-wrap:wrap;flex:1;gap:7px;display:flex}.Y6kksa_workspaceSelect{flex:1;min-width:0;display:block;position:relative}.Y6kksa_workspaceSelect>span{flex:1}.Y6kksa_filterRow .Y6kksa_workspaceTrigger{border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 82%, transparent);width:100%;min-height:30px;color:var(--dsw-alias-label-secondary);background:color-mix(in srgb, var(--dsw-alias-bg-overlay) 46%, transparent);font:inherit;text-align:left;cursor:pointer;border-radius:999px;justify-content:space-between;align-items:center;gap:10px;padding:3px 8px 3px 12px;font-size:12px;display:flex}.Y6kksa_filterRow .Y6kksa_workspaceTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.Y6kksa_filterRow .Y6kksa_workspaceTrigger>span{min-width:0;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.Y6kksa_workspaceChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}.Y6kksa_workspaceTrigger[aria-expanded=true] .Y6kksa_workspaceChevron{color:var(--tomato-accent,var(--dsw-alias-state-business-primary));transform:rotate(180deg)}.Y6kksa_error{color:var(--tomato-accent,var(--dsw-alias-state-error-primary));background:var(--dsw-alias-state-error-secondary);border-radius:12px;margin:16px 24px 0;padding:12px 14px}.Y6kksa_notice{color:var(--dsw-alias-label-secondary);margin:0;padding:8px 20px;font-size:11px}.Y6kksa_board{z-index:1;flex:1;align-items:flex-start;gap:12px;padding:8px 12px;display:flex;position:relative;overflow:auto}.Y6kksa_lane{border:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-bg-module-platform) 86%, transparent);width:300px;min-width:300px;max-height:100%;transition:transform .16s var(--ds-ease-in-out), opacity .16s var(--ds-ease-in-out), box-shadow .16s var(--ds-ease-in-out);border-radius:16px;flex-direction:column;display:flex}.Y6kksa_laneDragging{opacity:.5;transform:scale(.985)}.Y6kksa_laneDropBefore{box-shadow:-4px 0 0 var(--tomato-accent,var(--dsw-alias-state-business-primary));transform:translate(8px)}.Y6kksa_laneDropAfter{box-shadow:4px 0 0 var(--tomato-accent,var(--dsw-alias-state-business-primary));transform:translate(-8px)}.Y6kksa_laneHeader{cursor:grab;user-select:none;justify-content:space-between;align-items:center;padding:12px 14px 10px;display:flex}.Y6kksa_laneHeader:active{cursor:grabbing}@media (prefers-reduced-motion:reduce){.Y6kksa_lane{transition:none}}.Y6kksa_laneHeader h2{margin:0;font-size:14px}.Y6kksa_laneHeader span{text-align:center;min-width:22px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-overlay);border-radius:999px;padding:2px 6px;font-size:12px}.Y6kksa_cards{padding:0 8px 8px;overflow-y:auto}.Y6kksa_card{border:1px solid var(--dsw-alias-border-l2);min-width:0;max-width:100%;color:inherit;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);box-shadow:var(--dsw-shadow-lv1);text-align:left;transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out), border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out), box-shadow var(--ds-transition-duration-fast) var(--ds-ease-in-out);cursor:pointer;border-radius:14px;flex-direction:column;gap:7px;margin-top:8px;padding:12px;text-decoration:none;display:flex;overflow:hidden}.Y6kksa_card:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-button-floating-hover);box-shadow:var(--dsw-shadow-lv2)}.Y6kksa_cardMuted{opacity:.46;filter:saturate(.2);box-shadow:none}.Y6kksa_cardMuted:hover{opacity:.62}.Y6kksa_card:focus-visible{outline:2px solid var(--tomato-accent,var(--dsw-alias-state-business-primary));outline-offset:2px}.Y6kksa_card strong,.Y6kksa_key,.Y6kksa_meta{overflow-wrap:anywhere;word-break:normal;min-width:0}.Y6kksa_cardTopline{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.Y6kksa_cardButtons{align-items:center;gap:2px;display:inline-flex}.Y6kksa_muteButton,.Y6kksa_tomatoLink{border-radius:8px;flex:none;place-items:center;width:28px;min-width:28px;height:28px;min-height:28px;padding:0;line-height:1;display:inline-grid}.Y6kksa_muteButton{color:var(--dsw-alias-label-tertiary);font-size:9px}.Y6kksa_muteButton[aria-pressed=true]{color:var(--dsw-alias-label-primary)}.Y6kksa_card strong{font-size:14px;font-weight:600;line-height:1.45}.Y6kksa_key{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:650}.Y6kksa_cardMeta{flex-wrap:wrap;align-items:center;gap:5px;min-width:0;display:flex}.Y6kksa_empty{color:var(--dsw-alias-label-tertiary);margin:auto}@media (width<=820px){.Y6kksa_titleRow{flex-wrap:wrap}.Y6kksa_actions{flex-wrap:wrap;justify-content:flex-end}.Y6kksa_searchField{width:170px}}";
22
+ const tagId$2 = "@stephen1620/dsh-tomato-board/tomato-board.module.css";
23
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
24
24
  const tag = document.createElement("style");
25
25
  tag.dataset.plugin = "@stephen1620/dsh-tomato-board";
26
- tag.dataset.pluginCss = tagId;
27
- tag.textContent = css;
26
+ tag.dataset.pluginCss = tagId$2;
27
+ tag.textContent = css$2;
28
28
  document.head.appendChild(tag);
29
29
  }
30
30
  var tomato_board_module_css_default = {
31
31
  "actions": "Y6kksa_actions",
32
+ "assigneeDropdown": "Y6kksa_assigneeDropdown",
33
+ "assigneeFilter": "Y6kksa_assigneeFilter",
34
+ "assigneePicker": "Y6kksa_assigneePicker",
35
+ "assigneeTrigger": "Y6kksa_assigneeTrigger",
32
36
  "board": "Y6kksa_board",
33
37
  "card": "Y6kksa_card",
34
38
  "cardButtons": "Y6kksa_cardButtons",
39
+ "cardMeta": "Y6kksa_cardMeta",
35
40
  "cardMuted": "Y6kksa_cardMuted",
36
41
  "cardTopline": "Y6kksa_cardTopline",
37
42
  "cards": "Y6kksa_cards",
@@ -46,28 +51,745 @@ window.__ModuleLoader__.load({
46
51
  "headerIconButton": "Y6kksa_headerIconButton",
47
52
  "key": "Y6kksa_key",
48
53
  "lane": "Y6kksa_lane",
54
+ "laneDragging": "Y6kksa_laneDragging",
55
+ "laneDropAfter": "Y6kksa_laneDropAfter",
56
+ "laneDropBefore": "Y6kksa_laneDropBefore",
49
57
  "laneHeader": "Y6kksa_laneHeader",
50
58
  "meta": "Y6kksa_meta",
51
59
  "muteButton": "Y6kksa_muteButton",
52
60
  "notice": "Y6kksa_notice",
61
+ "pageTabs": "Y6kksa_pageTabs",
53
62
  "searchField": "Y6kksa_searchField",
54
- "selectedFilter": "Y6kksa_selectedFilter",
55
63
  "sidebarAction": "Y6kksa_sidebarAction",
64
+ "titleRow": "Y6kksa_titleRow",
56
65
  "tomatoIcon": "Y6kksa_tomatoIcon",
57
66
  "tomatoLink": "Y6kksa_tomatoLink",
67
+ "topbarAction": "Y6kksa_topbarAction",
58
68
  "transitionAction": "Y6kksa_transitionAction",
59
69
  "transitionCaption": "Y6kksa_transitionCaption",
60
70
  "transitionChevron": "Y6kksa_transitionChevron",
61
71
  "transitionDivider": "Y6kksa_transitionDivider",
62
72
  "transitionTrigger": "Y6kksa_transitionTrigger",
63
73
  "workbench": "Y6kksa_workbench",
74
+ "workspaceChevron": "Y6kksa_workspaceChevron",
75
+ "workspaceSelect": "Y6kksa_workspaceSelect",
64
76
  "workspaceTrigger": "Y6kksa_workspaceTrigger"
65
77
  };
66
78
  //#endregion
79
+ //#region \0dsh-css:/Users/gengfeng/deepseek-harness/packages/client/tomato-board/src/client/story-points.module.css.mjs
80
+ const css$1 = ".LfvjxG_page{--tomato-accent:var(--dsw-alias-state-business-primary,#3478c8);--tomato-accent-soft:color-mix(in srgb, var(--tomato-accent) 10%, transparent);min-height:0;color:var(--dsw-alias-label-primary,#292d32);flex:1;padding:14px 18px 18px;overflow:auto}.LfvjxG_page *{box-sizing:border-box}.LfvjxG_page button,.LfvjxG_page select,.LfvjxG_page input{font:inherit;color:inherit;border:1px solid var(--dsw-alias-border-l2,#dedfdf);background:var(--dsw-alias-bg-base,white);border-radius:8px;min-width:0;padding:8px 12px}.LfvjxG_page button{cursor:pointer;white-space:nowrap}.LfvjxG_page button:hover{background:var(--dsw-alias-interactive-bg-hover,#f4f4f2)}.LfvjxG_page button:disabled{opacity:.5;cursor:wait}.LfvjxG_page :focus-visible{outline:2px solid var(--tomato-accent);outline-offset:3px}.LfvjxG_page h3,.LfvjxG_page h4,.LfvjxG_page p{margin:0}.LfvjxG_panelHeader,.LfvjxG_listHeading,.LfvjxG_rankLabel{justify-content:space-between;align-items:center;gap:12px;display:flex}.LfvjxG_caption,.LfvjxG_footnote,.LfvjxG_itemText small,.LfvjxG_rankRow small,.LfvjxG_listHeading span{color:var(--dsw-alias-label-secondary,#797c80);font-size:12px;line-height:1.5}.LfvjxG_eyebrow{letter-spacing:1.6px;color:var(--dsw-alias-label-secondary,#797c80);font-size:10px;font-weight:650}.LfvjxG_headerControls{align-items:center;gap:8px;display:flex}.LfvjxG_headerControls label{color:var(--dsw-alias-label-secondary,#797c80);align-items:center;gap:7px;font-size:12px;display:flex}.LfvjxG_headerControls select{border:1px solid var(--dsw-alias-border-l2,#dedfdf);width:min(320px,34vw);min-width:0;height:28px;color:var(--dsw-alias-label-primary,#292d32);background:var(--dsw-alias-bg-base,white);font:inherit;border-radius:9px;padding:0 10px}.LfvjxG_headerIconButton{border-radius:8px;width:28px;min-width:28px;height:28px;min-height:28px;padding:0!important}.LfvjxG_columns{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);align-items:start;gap:14px;display:grid}.LfvjxG_teamsHeader{border-bottom:1px solid var(--dsw-alias-border-l2,#e4e4df);justify-content:space-between;align-items:center;padding-bottom:12px;display:flex}.LfvjxG_teamsHeader h3{font-size:16px}.LfvjxG_addTeamButton{height:30px;font-weight:600;min-width:0!important;padding:0 10px!important;font-size:12px!important}.LfvjxG_teamEditor{background:var(--dsw-alias-interactive-bg-hover,#f3f3f1);border-radius:9px;align-items:center;gap:7px;margin:9px 0;padding:8px;display:flex}.LfvjxG_teamEditor input{flex:1;min-width:0}.LfvjxG_teamEditor button{padding:6px 8px;font-size:11px}.LfvjxG_teamAccordions{gap:8px;margin-top:10px;display:grid}.LfvjxG_teamAccordion{border:1px solid var(--dsw-alias-border-l2,#e4e4df);border-radius:10px;overflow:visible}.LfvjxG_teamAccordionHeader{align-items:center;gap:2px;min-height:42px;padding:4px 6px;display:flex}.LfvjxG_teamToggle{text-align:left;align-items:center;gap:8px;min-width:0;display:flex;background:0 0!important;border:0!important;padding:6px!important}.LfvjxG_teamToggle strong{text-overflow:ellipsis;min-width:0;font-size:13px;overflow:hidden}.LfvjxG_teamToggle small{color:var(--dsw-alias-label-secondary);font-size:10px}.LfvjxG_renameTeam{width:27px;height:27px;margin-left:-3px;min-width:27px!important;color:var(--dsw-alias-label-secondary)!important;background:0 0!important;border-color:#0000!important;padding:0!important}.LfvjxG_renameTeam:hover{color:var(--tomato-accent)!important}.LfvjxG_chevron{color:var(--dsw-alias-label-tertiary);font-size:18px;line-height:1;transition:transform .16s}.LfvjxG_teamToggle[aria-expanded=true] .LfvjxG_chevron{color:var(--tomato-accent);transform:rotate(90deg)}.LfvjxG_teamRowActions{gap:2px;margin-left:auto;display:flex}.LfvjxG_teamRowActions button{width:27px;min-width:27px;height:27px;color:var(--dsw-alias-label-secondary);background:0 0;border-color:#0000;padding:0}.LfvjxG_teamRowActions button:last-child:hover{color:var(--dsw-alias-state-error-primary,#bd4336)}.LfvjxG_teamBody{border-top:1px solid var(--dsw-alias-border-l2,#e4e4df);padding:10px 12px 12px;position:relative}.LfvjxG_teamBodyToolbar{color:var(--dsw-alias-label-secondary);justify-content:space-between;align-items:center;gap:8px;font-size:11px;display:flex}.LfvjxG_smallAddButton{border-radius:6px!important;padding:4px 7px!important;font-size:10px!important}.LfvjxG_teamBodyEmpty{color:var(--dsw-alias-label-secondary);text-align:center;padding:20px 8px 10px;font-size:11px}.LfvjxG_memberRow{align-items:center;gap:4px;display:flex}.LfvjxG_memberRow .LfvjxG_rankLabel{flex:1;min-width:0}.LfvjxG_rankIndex{font-variant-numeric:tabular-nums;flex:0 0 22px;width:22px;color:var(--dsw-alias-label-tertiary)!important}.LfvjxG_memberAvatar{background:var(--tomato-accent-soft);border-radius:9px;flex:0 0 28px;place-items:center;width:28px;height:28px;font-weight:750;display:inline-grid;color:var(--tomato-accent)!important;font-size:12px!important}.LfvjxG_memberIdentity{z-index:1;flex:1;gap:1px;min-width:0;display:grid;position:relative}.LfvjxG_memberIdentity strong{text-overflow:ellipsis;overflow:hidden}.LfvjxG_memberIdentity small{color:var(--dsw-alias-label-tertiary);font-size:10px;font-weight:400}.LfvjxG_rankFill{z-index:0;border-radius:inherit;background:color-mix(in srgb, var(--tomato-accent) 11%, transparent);pointer-events:none;max-width:100%;position:absolute;inset:0 auto 0 0}.LfvjxG_rankLabel>.LfvjxG_rankIndex,.LfvjxG_rankLabel>.LfvjxG_memberAvatar,.LfvjxG_rankLabel>b{z-index:1;position:relative}.LfvjxG_removeMember{opacity:0;width:25px;height:25px;min-width:25px!important;color:var(--dsw-alias-label-tertiary)!important;background:0 0!important;border-color:#0000!important;padding:0!important}.LfvjxG_memberRow:hover .LfvjxG_removeMember,.LfvjxG_removeMember:focus-visible{opacity:1}.LfvjxG_removeMember:hover{color:var(--dsw-alias-state-error-primary,#bd4336)!important}.LfvjxG_panel{border:1px solid var(--dsw-alias-border-l2,#e4e4df);background:var(--dsw-alias-bg-base,white);border-radius:14px;min-width:0;padding:16px}.LfvjxG_panelHeader{border-bottom:1px solid var(--dsw-alias-border-l2,#e4e4df);padding-bottom:12px}.LfvjxG_panelHeader h3{white-space:nowrap;font-size:16px}.LfvjxG_panelHeader select{max-width:65%;font-size:12px}.LfvjxG_panelHeader button{font-size:12px}.LfvjxG_summary{justify-content:space-between;align-items:center;gap:16px;padding:16px 0;display:flex}.LfvjxG_total{font-variant-numeric:tabular-nums;letter-spacing:-2px;font-size:48px;font-weight:600;line-height:1.15}.LfvjxG_total small{letter-spacing:0;color:var(--dsw-alias-label-secondary,#797c80);font-size:15px}.LfvjxG_pie{background:var(--dsw-alias-border-l2,#e7e7e2);width:136px;height:136px;box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--dsw-alias-border-l2,#ddd) 55%, transparent), 0 14px 36px #00000012;border-radius:50%;flex:0 0 136px;padding:17px;position:relative;transform:rotate(-90deg)}.LfvjxG_pie:after{content:\"\";border:1px solid color-mix(in srgb, var(--tomato-accent) 24%, transparent);border-radius:50%;position:absolute;inset:-6px}.LfvjxG_pie>div{z-index:1;background:var(--dsw-alias-bg-base,white);width:100%;height:100%;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,#e7e7e2);border-radius:50%;flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative;transform:rotate(90deg)}.LfvjxG_pie strong{font-size:28px;line-height:1}.LfvjxG_pie span{letter-spacing:.08em;color:var(--dsw-alias-label-secondary,#797c80);margin-top:5px;font-size:10px}.LfvjxG_listHeading{padding:10px 0}.LfvjxG_listHeading h4{font-size:13px}.LfvjxG_item{border-top:1px solid var(--dsw-alias-border-l2,#e4e4df);align-items:center;gap:10px;padding:10px 12px;display:flex}.LfvjxG_dot{border-radius:3px;flex-shrink:0;width:8px;height:8px}.LfvjxG_itemText{flex:1;min-width:0}.LfvjxG_itemTitle{color:inherit;overflow-wrap:anywhere;font-size:13px;line-height:1.5;text-decoration:none}.LfvjxG_itemTitle:hover{text-decoration:underline}.LfvjxG_itemText small{margin-right:7px;font-size:10px;display:inline-block}.LfvjxG_itemPoints{white-space:nowrap;font-variant-numeric:tabular-nums;font-size:12px}.LfvjxG_item button{padding:5px 8px;font-size:11px}.LfvjxG_chips{flex-wrap:wrap;gap:8px;margin:12px 0 8px;display:flex}.LfvjxG_chips>span{background:var(--dsw-alias-interactive-bg-hover,#f2f3f0);border-radius:6px;align-items:center;gap:6px;padding:3px 5px 3px 10px;font-size:12px;display:flex}.LfvjxG_chips button{background:0 0;border:0;padding:1px 5px}.LfvjxG_ranking{gap:3px;margin-top:8px;display:grid}.LfvjxG_rankRow{border-radius:9px}.LfvjxG_rankRow:hover{background:color-mix(in srgb, var(--dsw-alias-interactive-bg-hover,#f1f2ee) 70%, transparent)}.LfvjxG_rankLabel{text-align:left;background:0 0;border:0;border-radius:9px;gap:8px;width:100%;min-height:48px;padding:6px 4px;font-size:13px;position:relative}.LfvjxG_rankLabel:hover{color:var(--tomato-accent);background:0 0!important}.LfvjxG_rankLabel[aria-pressed=true]{color:var(--tomato-accent);background:var(--tomato-accent-soft)!important}.LfvjxG_rankLabel b{font-variant-numeric:tabular-nums;flex:none;font-size:13px}.LfvjxG_track{display:none}.LfvjxG_footnote{border-top:1px solid var(--dsw-alias-border-l2,#e4e4df);padding-top:12px;font-size:11px;margin-top:20px!important}.LfvjxG_empty{text-align:center;color:var(--dsw-alias-label-secondary,#797c80);padding:36px 12px;font-size:13px}.LfvjxG_empty h4{margin-bottom:8px}.LfvjxG_emptyIcon{color:var(--tomato-accent);margin-bottom:12px;font-size:32px;display:block}.LfvjxG_error{color:var(--dsw-alias-state-error-primary,#bd4336);overflow-wrap:anywhere;padding:10px 0;font-size:12px}.LfvjxG_notice{color:var(--dsw-alias-label-secondary,#797c80);padding:8px 0;font-size:12px}.LfvjxG_teamHeader{overflow:visible}.LfvjxG_addPeopleRoot{position:relative}.LfvjxG_addPeople{z-index:30;border:1px solid var(--dsw-alias-border-l2,#ddd);background:var(--dsw-alias-bg-layer-2,white);border-radius:12px;width:min(330px,72vw);padding:10px;position:absolute;top:calc(100% + 7px);right:0;box-shadow:0 16px 46px #0002}.LfvjxG_addPeopleTitle{justify-content:space-between;align-items:center;gap:10px;display:flex;margin:0 0 8px!important}.LfvjxG_addPeopleTitle strong{font-size:12px}.LfvjxG_addPeopleTitle button{justify-content:center;min-width:26px;height:26px;width:26px!important;padding:0!important}.LfvjxG_addPeople input{width:100%}.LfvjxG_addPeople>div:last-child{max-height:210px;margin-top:8px;overflow:auto}.LfvjxG_addPeople>div:last-child>button{text-align:left;border:0;gap:8px;width:100%;display:flex}.LfvjxG_addPeople small{opacity:.6;flex:1}@media (width<=1000px){.LfvjxG_columns{grid-template-columns:1fr}.LfvjxG_page{padding:14px}}@media (width<=620px){.LfvjxG_teamHeader{flex-direction:column;align-items:flex-start}.LfvjxG_teamActions{justify-content:flex-start}.LfvjxG_memberToolbar{flex-direction:column;align-items:stretch}}@media (width<=540px){.LfvjxG_page{padding:10px}.LfvjxG_panel{padding:12px}.LfvjxG_headerControls label>span{display:none}.LfvjxG_headerControls select{width:min(220px,55vw)}.LfvjxG_pie{flex-basis:120px;width:120px;height:120px;padding:16px}.LfvjxG_total{font-size:42px}.LfvjxG_item{gap:7px}}.LfvjxG_item[role=button]{cursor:pointer;border-radius:8px}.LfvjxG_item[role=button]:hover{background:var(--dsw-alias-interactive-bg-hover)}.LfvjxG_item[role=button]:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.LfvjxG_item .LfvjxG_tomatoLink{background:0 0;border:0;flex:none;width:28px;height:28px;padding:0}";
81
+ const tagId$1 = "@stephen1620/dsh-tomato-board/story-points.module.css";
82
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
83
+ const tag = document.createElement("style");
84
+ tag.dataset.plugin = "@stephen1620/dsh-tomato-board";
85
+ tag.dataset.pluginCss = tagId$1;
86
+ tag.textContent = css$1;
87
+ document.head.appendChild(tag);
88
+ }
89
+ var story_points_module_css_default = {
90
+ "addPeople": "LfvjxG_addPeople",
91
+ "addPeopleRoot": "LfvjxG_addPeopleRoot",
92
+ "addPeopleTitle": "LfvjxG_addPeopleTitle",
93
+ "addTeamButton": "LfvjxG_addTeamButton",
94
+ "caption": "LfvjxG_caption",
95
+ "chevron": "LfvjxG_chevron",
96
+ "chips": "LfvjxG_chips",
97
+ "columns": "LfvjxG_columns",
98
+ "dot": "LfvjxG_dot",
99
+ "empty": "LfvjxG_empty",
100
+ "emptyIcon": "LfvjxG_emptyIcon",
101
+ "error": "LfvjxG_error",
102
+ "eyebrow": "LfvjxG_eyebrow",
103
+ "footnote": "LfvjxG_footnote",
104
+ "headerControls": "LfvjxG_headerControls",
105
+ "headerIconButton": "LfvjxG_headerIconButton",
106
+ "item": "LfvjxG_item",
107
+ "itemPoints": "LfvjxG_itemPoints",
108
+ "itemText": "LfvjxG_itemText",
109
+ "itemTitle": "LfvjxG_itemTitle",
110
+ "listHeading": "LfvjxG_listHeading",
111
+ "memberAvatar": "LfvjxG_memberAvatar",
112
+ "memberIdentity": "LfvjxG_memberIdentity",
113
+ "memberRow": "LfvjxG_memberRow",
114
+ "memberToolbar": "LfvjxG_memberToolbar",
115
+ "notice": "LfvjxG_notice",
116
+ "page": "LfvjxG_page",
117
+ "panel": "LfvjxG_panel",
118
+ "panelHeader": "LfvjxG_panelHeader",
119
+ "pie": "LfvjxG_pie",
120
+ "rankFill": "LfvjxG_rankFill",
121
+ "rankIndex": "LfvjxG_rankIndex",
122
+ "rankLabel": "LfvjxG_rankLabel",
123
+ "rankRow": "LfvjxG_rankRow",
124
+ "ranking": "LfvjxG_ranking",
125
+ "removeMember": "LfvjxG_removeMember",
126
+ "renameTeam": "LfvjxG_renameTeam",
127
+ "smallAddButton": "LfvjxG_smallAddButton",
128
+ "summary": "LfvjxG_summary",
129
+ "teamAccordion": "LfvjxG_teamAccordion",
130
+ "teamAccordionHeader": "LfvjxG_teamAccordionHeader",
131
+ "teamAccordions": "LfvjxG_teamAccordions",
132
+ "teamActions": "LfvjxG_teamActions",
133
+ "teamBody": "LfvjxG_teamBody",
134
+ "teamBodyEmpty": "LfvjxG_teamBodyEmpty",
135
+ "teamBodyToolbar": "LfvjxG_teamBodyToolbar",
136
+ "teamEditor": "LfvjxG_teamEditor",
137
+ "teamHeader": "LfvjxG_teamHeader",
138
+ "teamRowActions": "LfvjxG_teamRowActions",
139
+ "teamToggle": "LfvjxG_teamToggle",
140
+ "teamsHeader": "LfvjxG_teamsHeader",
141
+ "tomatoLink": "LfvjxG_tomatoLink",
142
+ "total": "LfvjxG_total",
143
+ "track": "LfvjxG_track"
144
+ };
145
+ //#endregion
146
+ //#region \0dsh-css:/Users/gengfeng/deepseek-harness/packages/client/tomato-board/src/client/tag.module.css.mjs
147
+ const css = ".vN3RbG_tag{--tag-border-color:color-mix(in srgb, var(--dsw-alias-border-l2) 82%, transparent);--tag-text-color:var(--dsw-alias-label-secondary);--tag-bg-color:color-mix(in srgb, var(--dsw-alias-bg-overlay) 46%, transparent);--tag-weight:550;overflow-wrap:anywhere;border:1px solid var(--tag-border-color);max-width:100%;color:var(--tag-text-color);background:var(--tag-bg-color);font-size:10px;font-weight:var(--tag-weight);white-space:normal;border-radius:999px;flex:none;padding:2px 9px;line-height:1.4}.vN3RbG_toned{--tag-border-color:color-mix(in srgb, var(--tag-tone) 38%, transparent);--tag-text-color:var(--tag-tone);--tag-bg-color:color-mix(in srgb, var(--tag-tone) 13%, transparent);--tag-weight:700}.vN3RbG_tag.vN3RbG_toggle{cursor:pointer;min-height:26px;font:inherit;font-size:10px;font-weight:var(--tag-weight);align-items:center;padding:2px 10px;line-height:1.4;display:inline-flex}.vN3RbG_tag.vN3RbG_toggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.vN3RbG_tag.vN3RbG_toned.vN3RbG_toggle:hover{background:color-mix(in srgb, var(--tag-tone) 18%, transparent)}.vN3RbG_tag.vN3RbG_toggle:focus-visible{outline:2px solid var(--tag-tone,var(--dsw-alias-state-business-primary));outline-offset:2px}.vN3RbG_off{opacity:.5;filter:saturate(.55)}";
148
+ const tagId = "@stephen1620/dsh-tomato-board/tag.module.css";
149
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
150
+ const tag = document.createElement("style");
151
+ tag.dataset.plugin = "@stephen1620/dsh-tomato-board";
152
+ tag.dataset.pluginCss = tagId;
153
+ tag.textContent = css;
154
+ document.head.appendChild(tag);
155
+ }
156
+ var tag_module_css_default = {
157
+ "off": "vN3RbG_off",
158
+ "tag": "vN3RbG_tag",
159
+ "toggle": "vN3RbG_toggle",
160
+ "toned": "vN3RbG_toned"
161
+ };
162
+ //#endregion
163
+ //#region src/client/Tag.tsx
164
+ /**
165
+ * The one tag chip used across the workbench — card metadata, filter chips and
166
+ * status labels. Static usage renders a span; interactive usage renders a
167
+ * toggle button. Only callers that pass `tone` get a colored chip (by
168
+ * convention just the item-type tags).
169
+ */
170
+ function Tag({ children, tone, interactive = false, pressed, title, className, onClick }) {
171
+ const style = tone === void 0 ? void 0 : { "--tag-tone": tone };
172
+ const classes = [
173
+ tag_module_css_default.tag,
174
+ tone === void 0 ? "" : tag_module_css_default.toned,
175
+ interactive ? tag_module_css_default.toggle : "",
176
+ interactive && pressed === false ? tag_module_css_default.off : "",
177
+ className
178
+ ].filter(Boolean).join(" ");
179
+ if (interactive) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
180
+ type: "button",
181
+ className: classes,
182
+ style,
183
+ title,
184
+ "aria-pressed": pressed,
185
+ onClick,
186
+ children
187
+ });
188
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
189
+ className: classes,
190
+ style,
191
+ title,
192
+ children
193
+ });
194
+ }
195
+ //#endregion
196
+ //#region src/client/StoryPoints.tsx
197
+ const LEGACY_TEAM_KEY = "taskboard.tomatoStoryTeam.v1";
198
+ const TEAMS_KEY = "taskboard.tomatoStoryTeams.v2";
199
+ const SPRINT_KEY = "taskboard.tomatoStorySprint.v1";
200
+ const OWNER_KEY = "taskboard.tomatoStoryOwner.v1";
201
+ const colors = [
202
+ "#d76b50",
203
+ "#458e88",
204
+ "#c79940",
205
+ "#687fb2",
206
+ "#976f9c",
207
+ "#75914f",
208
+ "#be7f92",
209
+ "#698b9d"
210
+ ];
211
+ const number = (value) => new Intl.NumberFormat("zh-CN", { maximumFractionDigits: 4 }).format(value);
212
+ const total = (items) => items.reduce((sum, item) => sum + (item.storyPoints ?? 0), 0);
213
+ const savedValue = (key, fallback) => {
214
+ try {
215
+ return localStorage.getItem(key) || fallback;
216
+ } catch {
217
+ return fallback;
218
+ }
219
+ };
220
+ const teamId = () => `team-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
221
+ async function json(url, signal) {
222
+ const response = await fetch(url, {
223
+ signal: signal ?? null,
224
+ cache: "no-store",
225
+ headers: {
226
+ accept: "application/json",
227
+ "cache-control": "no-cache"
228
+ }
229
+ });
230
+ const text = await response.text();
231
+ let body;
232
+ try {
233
+ body = text ? JSON.parse(text) : {};
234
+ } catch {
235
+ throw new Error(response.status === 404 || text.trim() === "not found" ? "迭代故事点接口尚未加载,请完全重启 DSH Desktop" : `服务返回了无法识别的内容 (${response.status})`);
236
+ }
237
+ if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `请求失败 (${response.status})`);
238
+ return body;
239
+ }
240
+ function readTeams() {
241
+ try {
242
+ const saved = JSON.parse(localStorage.getItem(TEAMS_KEY) ?? "[]");
243
+ if (Array.isArray(saved) && saved.length) return saved.flatMap((value) => value && typeof value === "object" ? [{
244
+ id: String(value.id),
245
+ name: String(value.name),
246
+ members: Array.isArray(value.members) ? [...new Set(value.members)] : []
247
+ }] : []);
248
+ const legacy = JSON.parse(localStorage.getItem(LEGACY_TEAM_KEY) ?? "[]");
249
+ return Array.isArray(legacy) && legacy.length ? [{
250
+ id: "team-default",
251
+ name: "默认团队",
252
+ members: legacy.filter((value) => typeof value === "string")
253
+ }] : [];
254
+ } catch {
255
+ return [];
256
+ }
257
+ }
258
+ function StoryPoints({ toolbarTarget, onOpenItem }) {
259
+ const [sprints, setSprints] = (0, react.useState)([]);
260
+ const [users, setUsers] = (0, react.useState)([]);
261
+ const [sprint, setSprint] = (0, react.useState)(() => savedValue(SPRINT_KEY, ""));
262
+ const [owner, setOwner] = (0, react.useState)(() => savedValue(OWNER_KEY, "currentUser()"));
263
+ const [teams, setTeams] = (0, react.useState)(readTeams);
264
+ const [openTeamId, setOpenTeamId] = (0, react.useState)(() => readTeams()[0]?.id ?? "");
265
+ const [loadedTeams, setLoadedTeams] = (0, react.useState)(() => {
266
+ const first = readTeams()[0]?.id;
267
+ return new Set(first ? [first] : []);
268
+ });
269
+ const [teamDraft, setTeamDraft] = (0, react.useState)("");
270
+ const [teamEditor, setTeamEditor] = (0, react.useState)(null);
271
+ const [addingTeamId, setAddingTeamId] = (0, react.useState)("");
272
+ const addPeopleRef = (0, react.useRef)(null);
273
+ const addPeopleInputRef = (0, react.useRef)(null);
274
+ const [search, setSearch] = (0, react.useState)("");
275
+ const [results, setResults] = (0, react.useState)({});
276
+ const [errors, setErrors] = (0, react.useState)({});
277
+ const [directoryError, setDirectoryError] = (0, react.useState)("");
278
+ const [directoryLoading, setDirectoryLoading] = (0, react.useState)(true);
279
+ const [version, setVersion] = (0, react.useState)(0);
280
+ const [directoryVersion, setDirectoryVersion] = (0, react.useState)(0);
281
+ (0, react.useEffect)(() => {
282
+ const controller = new AbortController();
283
+ setDirectoryLoading(true);
284
+ setDirectoryError("");
285
+ Promise.all([json("/api/tomato-board/sprints", controller.signal), json("/api/tomato-board/filters", controller.signal)]).then(([data, directory]) => {
286
+ const sorted = [...data.sprints].sort((a, b) => (b.startDate ?? "").localeCompare(a.startDate ?? ""));
287
+ setSprints(sorted);
288
+ setUsers(directory.users);
289
+ setSprint((current) => sorted.some((value) => value.sprintId === current) ? current : sorted[0]?.sprintId || "");
290
+ }).catch((error) => {
291
+ if (!controller.signal.aborted) setDirectoryError(error.message);
292
+ }).finally(() => {
293
+ if (!controller.signal.aborted) setDirectoryLoading(false);
294
+ });
295
+ return () => controller.abort();
296
+ }, [directoryVersion]);
297
+ (0, react.useEffect)(() => {
298
+ try {
299
+ localStorage.setItem(TEAMS_KEY, JSON.stringify(teams));
300
+ } catch {}
301
+ }, [teams]);
302
+ (0, react.useEffect)(() => {
303
+ if (sprint) try {
304
+ localStorage.setItem(SPRINT_KEY, sprint);
305
+ } catch {}
306
+ }, [sprint]);
307
+ (0, react.useEffect)(() => {
308
+ try {
309
+ localStorage.setItem(OWNER_KEY, owner);
310
+ } catch {}
311
+ }, [owner]);
312
+ const openMembers = teams.find((team) => team.id === openTeamId)?.members ?? [];
313
+ const ownersKey = JSON.stringify([...new Set([owner, ...loadedTeams.has(openTeamId) ? openMembers : []])].sort());
314
+ (0, react.useEffect)(() => {
315
+ if (!sprint) return;
316
+ const controller = new AbortController();
317
+ const owners = JSON.parse(ownersKey).filter((username) => !results[username] && !errors[username]);
318
+ const worker = async () => {
319
+ while (owners.length && !controller.signal.aborted) {
320
+ const username = owners.shift();
321
+ try {
322
+ const data = await json(`/api/tomato-board/story-points?${new URLSearchParams({
323
+ sprint,
324
+ assignee: username,
325
+ refresh: String(version)
326
+ })}`, controller.signal);
327
+ if (!controller.signal.aborted) setResults((current) => ({
328
+ ...current,
329
+ [username]: data
330
+ }));
331
+ } catch (error) {
332
+ if (!controller.signal.aborted) setErrors((current) => ({
333
+ ...current,
334
+ [username]: error instanceof Error ? error.message : "读取失败"
335
+ }));
336
+ }
337
+ }
338
+ };
339
+ Promise.all([
340
+ worker(),
341
+ worker(),
342
+ worker()
343
+ ]);
344
+ return () => controller.abort();
345
+ }, [
346
+ sprint,
347
+ ownersKey,
348
+ version
349
+ ]);
350
+ const personal = results[owner];
351
+ const items = (0, react.useMemo)(() => [...personal?.items ?? []].sort((a, b) => (b.storyPoints ?? -1) - (a.storyPoints ?? -1) || a.itemKey.localeCompare(b.itemKey)), [personal]);
352
+ const sum = total(items);
353
+ const slices = items.filter((item) => item.storyPoints !== null && item.storyPoints > 0);
354
+ let offset = 0;
355
+ const gradient = slices.map((item, index) => {
356
+ const start = offset;
357
+ offset += item.storyPoints / sum * 100;
358
+ return `${colors[index % colors.length]} ${start}% ${offset}%`;
359
+ }).join(", ");
360
+ const userName = (username) => username === "currentUser()" ? "我" : users.find((user) => user.username === username)?.name ?? username;
361
+ const toggleTeam = (id) => {
362
+ setOpenTeamId((current) => current === id ? "" : id);
363
+ setLoadedTeams((current) => new Set(current).add(id));
364
+ setAddingTeamId("");
365
+ setTeamEditor(null);
366
+ };
367
+ const ranking = openMembers.map((username) => ({
368
+ username,
369
+ result: results[username],
370
+ points: total(results[username]?.items ?? [])
371
+ })).sort((a, b) => Number(Boolean(b.result)) - Number(Boolean(a.result)) || b.points - a.points || a.username.localeCompare(b.username));
372
+ const max = Math.max(1, ...ranking.map((row) => row.points));
373
+ const available = users.filter((user) => !openMembers.includes(user.username) && `${user.name} ${user.username}`.toLowerCase().includes(search.toLowerCase()));
374
+ const submitTeam = () => {
375
+ const name = teamDraft.trim();
376
+ if (!name || !teamEditor) return;
377
+ if (teamEditor.mode === "create") {
378
+ const team = {
379
+ id: teamId(),
380
+ name,
381
+ members: []
382
+ };
383
+ setTeams((current) => [...current, team]);
384
+ setOpenTeamId(team.id);
385
+ setLoadedTeams((current) => new Set(current).add(team.id));
386
+ } else setTeams((current) => current.map((team) => team.id === teamEditor.id ? {
387
+ ...team,
388
+ name
389
+ } : team));
390
+ setTeamDraft("");
391
+ setTeamEditor(null);
392
+ };
393
+ const deleteTeam = (id) => {
394
+ setTeams((current) => current.filter((team) => team.id !== id));
395
+ if (openTeamId === id) setOpenTeamId("");
396
+ setLoadedTeams((current) => {
397
+ const next = new Set(current);
398
+ next.delete(id);
399
+ return next;
400
+ });
401
+ };
402
+ const updateMembers = (id, updater) => setTeams((current) => current.map((team) => team.id === id ? {
403
+ ...team,
404
+ members: updater(team.members)
405
+ } : team));
406
+ (0, react.useEffect)(() => {
407
+ if (!addingTeamId) return;
408
+ const close = (event) => {
409
+ if (event.target instanceof Node && !addPeopleRef.current?.contains(event.target)) {
410
+ setAddingTeamId("");
411
+ setSearch("");
412
+ }
413
+ };
414
+ document.addEventListener("pointerdown", close, true);
415
+ requestAnimationFrame(() => addPeopleInputRef.current?.focus());
416
+ return () => document.removeEventListener("pointerdown", close, true);
417
+ }, [addingTeamId]);
418
+ (0, react.useEffect)(() => {
419
+ toolbarTarget(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
420
+ className: story_points_module_css_default.headerControls,
421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "迭代" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
422
+ "aria-label": "选择迭代",
423
+ value: sprint,
424
+ disabled: directoryLoading,
425
+ onChange: (event) => {
426
+ setSprint(event.target.value);
427
+ setResults({});
428
+ setErrors({});
429
+ setLoadedTeams(/* @__PURE__ */ new Set());
430
+ },
431
+ children: [!sprints.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
432
+ value: "",
433
+ children: directoryLoading ? "正在读取迭代…" : "暂无迭代"
434
+ }), sprints.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
435
+ value: value.sprintId,
436
+ children: [
437
+ value.name,
438
+ " · ",
439
+ value.workspaceKey,
440
+ value.status === "completed" ? "(已结束)" : ""
441
+ ]
442
+ }, value.sprintId))]
443
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
444
+ variant: "toolbar",
445
+ size: "sm",
446
+ className: story_points_module_css_default.headerIconButton,
447
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}),
448
+ title: "刷新",
449
+ "aria-label": "刷新迭代故事点",
450
+ onClick: () => {
451
+ setResults({});
452
+ setErrors({});
453
+ setVersion((value) => value + 1);
454
+ if (directoryError || !sprints.length) setDirectoryVersion((value) => value + 1);
455
+ }
456
+ })]
457
+ }));
458
+ return () => toolbarTarget(null);
459
+ }, [
460
+ directoryError,
461
+ directoryLoading,
462
+ sprint,
463
+ sprints,
464
+ toolbarTarget
465
+ ]);
466
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
467
+ className: story_points_module_css_default.page,
468
+ children: [directoryError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
469
+ className: story_points_module_css_default.error,
470
+ children: [directoryError, ",请点击刷新重试。"]
471
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
472
+ className: story_points_module_css_default.columns,
473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
474
+ className: story_points_module_css_default.panel,
475
+ "aria-label": "个人故事点",
476
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
477
+ className: story_points_module_css_default.panelHeader,
478
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "个人分布" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
479
+ "aria-label": "选择个人负责人",
480
+ value: owner,
481
+ onChange: (event) => setOwner(event.target.value),
482
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
483
+ value: "currentUser()",
484
+ children: "我负责的"
485
+ }), users.map((user) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
486
+ value: user.username,
487
+ children: [
488
+ user.name,
489
+ " · ",
490
+ user.username
491
+ ]
492
+ }, user.username))]
493
+ })]
494
+ }), errors[owner] ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
495
+ className: story_points_module_css_default.error,
496
+ children: errors[owner]
497
+ }) : !personal ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
498
+ className: story_points_module_css_default.empty,
499
+ children: sprint ? "正在读取个人故事点…" : "请选择迭代"
500
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
502
+ className: story_points_module_css_default.summary,
503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
504
+ className: story_points_module_css_default.eyebrow,
505
+ children: "故事点总数"
506
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
507
+ className: story_points_module_css_default.total,
508
+ children: [number(sum), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: " SP" })]
509
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
510
+ className: story_points_module_css_default.pie,
511
+ style: { background: gradient ? `conic-gradient(${gradient})` : void 0 },
512
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: slices.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "项占比" })] })
513
+ })]
514
+ }),
515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
516
+ className: story_points_module_css_default.listHeading,
517
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "需求列表" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [items.length, " 项"] })]
518
+ }),
519
+ !items.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
520
+ className: story_points_module_css_default.empty,
521
+ children: "该负责人在本迭代暂无事项。"
522
+ }),
523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
524
+ className: story_points_module_css_default.item,
525
+ role: "button",
526
+ tabIndex: 0,
527
+ "aria-label": `打开 ${item.itemKey} 的对话`,
528
+ onClick: () => onOpenItem(item),
529
+ onKeyDown: (event) => {
530
+ if (event.key === "Enter" || event.key === " ") {
531
+ event.preventDefault();
532
+ onOpenItem(item);
533
+ }
534
+ },
535
+ children: [
536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
537
+ className: story_points_module_css_default.dot,
538
+ style: { background: item.storyPoints && item.storyPoints > 0 ? colors[slices.indexOf(item) % colors.length] : "var(--dsw-alias-border-l2, #ddd)" }
539
+ }),
540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
541
+ className: story_points_module_css_default.itemText,
542
+ children: [
543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
544
+ className: story_points_module_css_default.itemTitle,
545
+ children: item.title
546
+ }),
547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: item.itemKey }),
548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, { children: item.status })
549
+ ]
550
+ }),
551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
552
+ className: story_points_module_css_default.itemPoints,
553
+ children: item.storyPoints === null ? "未估点" : `${number(item.storyPoints)} SP`
554
+ }),
555
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
556
+ className: story_points_module_css_default.tomatoLink,
557
+ variant: "ghost",
558
+ size: "sm",
559
+ title: "在番茄中打开事项",
560
+ "aria-label": `在番茄中打开 ${item.itemKey}`,
561
+ onClick: (event) => {
562
+ event.stopPropagation();
563
+ window.open(item.tomatoUrl, "_blank", "noopener,noreferrer");
564
+ },
565
+ onKeyDown: (event) => event.stopPropagation(),
566
+ children: "↗"
567
+ })
568
+ ]
569
+ }, item.itemKey)) })
570
+ ] })]
571
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
572
+ className: story_points_module_css_default.panel,
573
+ "aria-label": "团队故事点",
574
+ children: [
575
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
576
+ className: story_points_module_css_default.teamsHeader,
577
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "团队分布" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
578
+ className: story_points_module_css_default.addTeamButton,
579
+ "aria-label": "创建团队",
580
+ title: "创建团队",
581
+ onClick: () => {
582
+ setTeamEditor({ mode: "create" });
583
+ setTeamDraft("");
584
+ },
585
+ children: "+ 创建团队"
586
+ })]
587
+ }),
588
+ teamEditor?.mode === "create" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
589
+ className: story_points_module_css_default.teamEditor,
590
+ onSubmit: (event) => {
591
+ event.preventDefault();
592
+ submitTeam();
593
+ },
594
+ children: [
595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
596
+ autoFocus: true,
597
+ placeholder: "团队名称",
598
+ value: teamDraft,
599
+ onChange: (event) => setTeamDraft(event.target.value)
600
+ }),
601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
602
+ type: "button",
603
+ onClick: () => setTeamEditor(null),
604
+ children: "取消"
605
+ }),
606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
607
+ type: "submit",
608
+ children: "创建"
609
+ })
610
+ ]
611
+ }),
612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
613
+ className: story_points_module_css_default.teamAccordions,
614
+ children: teams.map((team) => {
615
+ const open = openTeamId === team.id;
616
+ const rows = open ? ranking : [];
617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
618
+ className: story_points_module_css_default.teamAccordion,
619
+ children: [
620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
621
+ className: story_points_module_css_default.teamAccordionHeader,
622
+ children: [
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
624
+ className: story_points_module_css_default.teamToggle,
625
+ "aria-expanded": open,
626
+ onClick: () => toggleTeam(team.id),
627
+ children: [
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
629
+ className: story_points_module_css_default.chevron,
630
+ children: "›"
631
+ }),
632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: team.name }),
633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", { children: [team.members.length, " 人"] })
634
+ ]
635
+ }),
636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
637
+ className: story_points_module_css_default.renameTeam,
638
+ "aria-label": `改名 ${team.name}`,
639
+ title: "改名",
640
+ onClick: () => {
641
+ setTeamEditor({
642
+ mode: "rename",
643
+ id: team.id
644
+ });
645
+ setTeamDraft(team.name);
646
+ setOpenTeamId(team.id);
647
+ },
648
+ children: "✎"
649
+ }),
650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
651
+ className: story_points_module_css_default.teamRowActions,
652
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
653
+ "aria-label": `删除 ${team.name}`,
654
+ title: "删除",
655
+ onClick: () => deleteTeam(team.id),
656
+ children: "×"
657
+ })
658
+ })
659
+ ]
660
+ }),
661
+ teamEditor?.mode === "rename" && teamEditor.id === team.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
662
+ className: story_points_module_css_default.teamEditor,
663
+ onSubmit: (event) => {
664
+ event.preventDefault();
665
+ submitTeam();
666
+ },
667
+ children: [
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
669
+ autoFocus: true,
670
+ value: teamDraft,
671
+ onChange: (event) => setTeamDraft(event.target.value)
672
+ }),
673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
674
+ type: "button",
675
+ onClick: () => setTeamEditor(null),
676
+ children: "取消"
677
+ }),
678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
679
+ type: "submit",
680
+ children: "保存"
681
+ })
682
+ ]
683
+ }),
684
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
685
+ className: story_points_module_css_default.teamBody,
686
+ children: [
687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
688
+ className: story_points_module_css_default.teamBodyToolbar,
689
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: team.members.length ? "成员故事点" : "暂无成员" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
690
+ ref: addingTeamId === team.id ? addPeopleRef : void 0,
691
+ className: story_points_module_css_default.addPeopleRoot,
692
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
693
+ className: story_points_module_css_default.smallAddButton,
694
+ onClick: () => setAddingTeamId((current) => current === team.id ? "" : team.id),
695
+ children: "+ 添加成员"
696
+ }), addingTeamId === team.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
697
+ className: story_points_module_css_default.addPeople,
698
+ role: "dialog",
699
+ children: [
700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
701
+ className: story_points_module_css_default.addPeopleTitle,
702
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "添加成员" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
703
+ onClick: () => setAddingTeamId(""),
704
+ children: "×"
705
+ })]
706
+ }),
707
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
708
+ ref: addPeopleInputRef,
709
+ value: search,
710
+ onChange: (event) => setSearch(event.target.value),
711
+ placeholder: "搜索姓名或用户名…"
712
+ }),
713
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [available.map((user) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
714
+ onClick: () => {
715
+ updateMembers(team.id, (current) => [...current, user.username]);
716
+ setAddingTeamId("");
717
+ setSearch("");
718
+ },
719
+ children: [
720
+ user.name,
721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: user.username }),
722
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "+" })
723
+ ]
724
+ }, user.username)), !available.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "没有可添加的成员" })] })
725
+ ]
726
+ })]
727
+ })]
728
+ }),
729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
730
+ className: story_points_module_css_default.ranking,
731
+ children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
732
+ className: story_points_module_css_default.rankRow,
733
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
734
+ className: story_points_module_css_default.memberRow,
735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
736
+ className: story_points_module_css_default.rankLabel,
737
+ "aria-pressed": owner === row.username,
738
+ onClick: () => setOwner(row.username),
739
+ children: [
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
741
+ className: story_points_module_css_default.rankFill,
742
+ style: { width: `${row.points / max * 100}%` }
743
+ }),
744
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
745
+ className: story_points_module_css_default.rankIndex,
746
+ children: String(index + 1).padStart(2, "0")
747
+ }),
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
749
+ className: story_points_module_css_default.memberAvatar,
750
+ children: userName(row.username).slice(0, 1)
751
+ }),
752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
753
+ className: story_points_module_css_default.memberIdentity,
754
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: userName(row.username) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: row.result ? `${row.result.items.length} 个事项` : errors[row.username] ? "读取失败" : "读取中…" })]
755
+ }),
756
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: row.result ? `${number(row.points)} SP` : "—" })
757
+ ]
758
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
759
+ className: story_points_module_css_default.removeMember,
760
+ "aria-label": `移除 ${userName(row.username)}`,
761
+ title: "移除成员",
762
+ onClick: () => updateMembers(team.id, (current) => current.filter((value) => value !== row.username)),
763
+ children: "×"
764
+ })]
765
+ })
766
+ }, row.username))
767
+ }),
768
+ !team.members.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
769
+ className: story_points_module_css_default.teamBodyEmpty,
770
+ children: "添加成员后查看故事点分布"
771
+ })
772
+ ]
773
+ })
774
+ ]
775
+ }, team.id);
776
+ })
777
+ }),
778
+ !teams.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
779
+ className: story_points_module_css_default.empty,
780
+ children: "点击右上角加号创建团队。"
781
+ })
782
+ ]
783
+ })]
784
+ })]
785
+ });
786
+ }
787
+ //#endregion
67
788
  //#region src/client/TomatoBoard.tsx
68
789
  let state = {
69
790
  open: false,
70
791
  loading: false,
792
+ loaded: false,
71
793
  items: [],
72
794
  error: null,
73
795
  selectedItem: null,
@@ -88,14 +810,6 @@ window.__ModuleLoader__.load({
88
810
  };
89
811
  const snapshot = () => state;
90
812
  const TOMATO_ITEM_KEY_PATTERN = /^\[([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*-\d+)\]/u;
91
- const TOMATO_TYPE_OPTIONS = [
92
- "测试缺陷",
93
- "缺陷",
94
- "Bug",
95
- "EnablerStory",
96
- "Story",
97
- "Task"
98
- ];
99
813
  const TOMATO_STATUS_ORDER = [
100
814
  "新建",
101
815
  "Bugfix",
@@ -115,6 +829,48 @@ window.__ModuleLoader__.load({
115
829
  const TOMATO_FILTER_BLACKLIST_KEY = "taskboard.tomatoFilterBlacklist.v1";
116
830
  const TOMATO_SESSION_LINKS_KEY = "taskboard.tomatoSessionLinks.v1";
117
831
  const TOMATO_MUTED_ITEMS_KEY = "taskboard.tomatoMutedItems.v1";
832
+ const TOMATO_LANE_ORDER_KEY = "taskboard.tomatoLaneOrder.v1";
833
+ function readLaneOrder() {
834
+ try {
835
+ const value = JSON.parse(window.localStorage.getItem(TOMATO_LANE_ORDER_KEY) ?? "{}");
836
+ return {
837
+ head: typeof value?.head === "string" ? value.head : null,
838
+ next: value?.next && typeof value.next === "object" ? value.next : {}
839
+ };
840
+ } catch {
841
+ return {
842
+ head: null,
843
+ next: {}
844
+ };
845
+ }
846
+ }
847
+ function laneOrderValues(order) {
848
+ const values = [];
849
+ const seen = /* @__PURE__ */ new Set();
850
+ let current = order.head;
851
+ while (current && !seen.has(current) && values.length < 1e3) {
852
+ values.push(current);
853
+ seen.add(current);
854
+ current = order.next[current] ?? null;
855
+ }
856
+ return values;
857
+ }
858
+ function createLaneOrder(values) {
859
+ const next = {};
860
+ values.forEach((value, index) => {
861
+ next[value] = values[index + 1] ?? null;
862
+ });
863
+ return {
864
+ head: values[0] ?? null,
865
+ next
866
+ };
867
+ }
868
+ function applyLaneOrder(statuses, order) {
869
+ const available = new Set(statuses);
870
+ const tracked = laneOrderValues(order).filter((status) => available.has(status));
871
+ const trackedSet = new Set(tracked);
872
+ return [...tracked, ...statuses.filter((status) => !trackedSet.has(status))];
873
+ }
118
874
  function readSessionLinks() {
119
875
  try {
120
876
  const value = JSON.parse(window.localStorage.getItem(TOMATO_SESSION_LINKS_KEY) ?? "{}");
@@ -137,12 +893,14 @@ window.__ModuleLoader__.load({
137
893
  const value = JSON.parse(window.localStorage.getItem(TOMATO_FILTER_BLACKLIST_KEY) ?? "{}");
138
894
  return {
139
895
  types: new Set(Array.isArray(value?.types) ? value.types.filter((item) => typeof item === "string") : []),
140
- statuses: new Set(Array.isArray(value?.statuses) ? value.statuses.filter((item) => typeof item === "string") : [])
896
+ statuses: new Set(Array.isArray(value?.statuses) ? value.statuses.filter((item) => typeof item === "string") : []),
897
+ workspaces: new Set(Array.isArray(value?.workspaces) ? value.workspaces.filter((item) => typeof item === "string") : [])
141
898
  };
142
899
  } catch {
143
900
  return {
144
901
  types: /* @__PURE__ */ new Set(),
145
- statuses: /* @__PURE__ */ new Set()
902
+ statuses: /* @__PURE__ */ new Set(),
903
+ workspaces: /* @__PURE__ */ new Set()
146
904
  };
147
905
  }
148
906
  }
@@ -154,18 +912,20 @@ window.__ModuleLoader__.load({
154
912
  return /* @__PURE__ */ new Set();
155
913
  }
156
914
  }
157
- async function refresh() {
915
+ async function refresh(assignee = "currentUser()") {
158
916
  emit({
159
917
  loading: true,
160
918
  error: null
161
919
  });
162
920
  try {
163
- const response = await fetch("/api/tomato-board/items", { headers: { accept: "application/json" } });
921
+ const query = new URLSearchParams({ assignee });
922
+ const response = await fetch(`/api/tomato-board/items?${query}`, { headers: { accept: "application/json" } });
164
923
  const body = await response.json();
165
924
  if (!response.ok) throw new Error(body.error || `请求失败 (${response.status})`);
166
925
  emit({
167
926
  items: body.items ?? [],
168
- truncated: body.truncated === true
927
+ truncated: body.truncated === true,
928
+ loaded: true
169
929
  });
170
930
  } catch (error) {
171
931
  emit({ error: error instanceof Error ? error.message : "番茄事项读取失败" });
@@ -178,7 +938,9 @@ window.__ModuleLoader__.load({
178
938
  disposeWorkbench = null;
179
939
  emit({
180
940
  open: false,
181
- selectedItem: null
941
+ selectedItem: null,
942
+ loaded: false,
943
+ error: null
182
944
  });
183
945
  dispose?.();
184
946
  }
@@ -195,6 +957,19 @@ window.__ModuleLoader__.load({
195
957
  }), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "番茄工作台" })]
196
958
  });
197
959
  }
960
+ function TomatoBoardTopbarAction({ openWorkbench }) {
961
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
962
+ className: tomato_board_module_css_default.topbarAction,
963
+ type: "button",
964
+ title: "打开番茄工作台",
965
+ onClick: openWorkbench,
966
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
967
+ className: tomato_board_module_css_default.tomatoIcon,
968
+ "aria-hidden": "true",
969
+ children: "T"
970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "番茄工作台" })]
971
+ });
972
+ }
198
973
  function CreateConversationDialog({ ctx, item }) {
199
974
  const [workspaceId, setWorkspaceId] = (0, react.useState)("");
200
975
  const [workspaceMenuOpen, setWorkspaceMenuOpen] = (0, react.useState)(false);
@@ -293,18 +1068,36 @@ window.__ModuleLoader__.load({
293
1068
  function TomatoBoardPanel({ ctx }) {
294
1069
  const board = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
295
1070
  const workbenchRef = (0, react.useRef)(null);
1071
+ const filterMenuRef = (0, react.useRef)(null);
1072
+ const [page, setPage] = (0, react.useState)("board");
1073
+ const [storyToolbar, setStoryToolbar] = (0, react.useState)(null);
296
1074
  const [search, setSearch] = (0, react.useState)("");
297
1075
  const [blacklist, setBlacklist] = (0, react.useState)(readFilterBlacklist);
298
1076
  const [mutedItems, setMutedItems] = (0, react.useState)(readMutedItems);
1077
+ const [selectedAssignee, setSelectedAssignee] = (0, react.useState)("currentUser()");
1078
+ const [filterDirectory, setFilterDirectory] = (0, react.useState)({
1079
+ users: [],
1080
+ workspaces: []
1081
+ });
1082
+ const [directoryVersion, setDirectoryVersion] = (0, react.useState)(0);
1083
+ const [laneOrder, setLaneOrder] = (0, react.useState)(readLaneOrder);
1084
+ const [draggedLane, setDraggedLane] = (0, react.useState)(null);
1085
+ const [dropLane, setDropLane] = (0, react.useState)(null);
1086
+ const laneElements = (0, react.useRef)(/* @__PURE__ */ new Map());
1087
+ const previousLanePositions = (0, react.useRef)(/* @__PURE__ */ new Map());
299
1088
  const sessions = (0, react.useSyncExternalStore)((listener) => ctx.sessions.list.subscribe(listener), () => ctx.sessions.list.getSnapshot(), () => ctx.sessions.list.getSnapshot());
300
1089
  (0, react.useEffect)(() => {
301
- if (board.open && board.items.length === 0 && !board.loading && !board.error) refresh();
1090
+ if (board.open && !board.loaded && !board.loading && !board.error) refresh();
302
1091
  }, [
303
1092
  board.error,
304
- board.items.length,
1093
+ board.loaded,
305
1094
  board.loading,
306
1095
  board.open
307
1096
  ]);
1097
+ (0, react.useEffect)(() => {
1098
+ if (!board.open) return;
1099
+ fetch("/api/tomato-board/filters", { headers: { accept: "application/json" } }).then((response) => response.ok ? response.json() : Promise.reject(/* @__PURE__ */ new Error(`HTTP ${response.status}`))).then((value) => setFilterDirectory(value)).catch(() => {});
1100
+ }, [board.open, directoryVersion]);
308
1101
  (0, react.useEffect)(() => {
309
1102
  if (!board.open) return;
310
1103
  const closeOnOutsideNavigation = (event) => {
@@ -316,6 +1109,41 @@ window.__ModuleLoader__.load({
316
1109
  document.addEventListener("pointerdown", closeOnOutsideNavigation, true);
317
1110
  return () => document.removeEventListener("pointerdown", closeOnOutsideNavigation, true);
318
1111
  }, [board.open]);
1112
+ (0, react.useEffect)(() => {
1113
+ const closeFilterMenu = (event) => {
1114
+ const menu = filterMenuRef.current;
1115
+ if (menu?.open && event.target instanceof Node && !menu.contains(event.target)) menu.open = false;
1116
+ };
1117
+ const closeFilterMenuOnEscape = (event) => {
1118
+ if (event.key === "Escape" && filterMenuRef.current?.open) filterMenuRef.current.open = false;
1119
+ };
1120
+ document.addEventListener("pointerdown", closeFilterMenu, true);
1121
+ document.addEventListener("keydown", closeFilterMenuOnEscape);
1122
+ return () => {
1123
+ document.removeEventListener("pointerdown", closeFilterMenu, true);
1124
+ document.removeEventListener("keydown", closeFilterMenuOnEscape);
1125
+ };
1126
+ }, []);
1127
+ (0, react.useLayoutEffect)(() => {
1128
+ const previous = previousLanePositions.current;
1129
+ if (previous.size === 0) return;
1130
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1131
+ previous.clear();
1132
+ return;
1133
+ }
1134
+ for (const [status, element] of laneElements.current) {
1135
+ const before = previous.get(status);
1136
+ if (!before) continue;
1137
+ const after = element.getBoundingClientRect();
1138
+ const deltaX = before.left - after.left;
1139
+ const deltaY = before.top - after.top;
1140
+ if (deltaX || deltaY) element.animate([{ transform: `translate(${deltaX}px, ${deltaY}px)` }, { transform: "translate(0, 0)" }], {
1141
+ duration: 240,
1142
+ easing: "cubic-bezier(.2,.8,.2,1)"
1143
+ });
1144
+ }
1145
+ previous.clear();
1146
+ }, [laneOrder]);
319
1147
  if (!board.open) return null;
320
1148
  function openItem(item) {
321
1149
  const stored = linkedSessionId(item.itemKey);
@@ -334,32 +1162,55 @@ window.__ModuleLoader__.load({
334
1162
  emit({ selectedItem: item });
335
1163
  }
336
1164
  const normalizedSearch = search.trim().toLowerCase();
337
- const filteredItems = board.items.filter((item) => !blacklist.types.has(item.itemType) && !blacklist.statuses.has(item.status) && (!normalizedSearch || [
1165
+ const filteredItems = board.items.filter((item) => !blacklist.types.has(item.itemType) && !blacklist.statuses.has(item.status) && !blacklist.workspaces.has(item.workspaceKey) && !blacklist.workspaces.has(item.workspaceName) && (!normalizedSearch || [
338
1166
  item.itemKey,
339
1167
  item.title,
340
1168
  item.itemType,
341
1169
  item.status,
342
- item.workspace,
1170
+ item.workspaceKey,
1171
+ item.workspaceName,
343
1172
  item.creator,
1173
+ item.assignees.join(" "),
344
1174
  item.priority
345
1175
  ].join(" ").toLowerCase().includes(normalizedSearch)));
346
- const typeOptions = [...new Set([...TOMATO_TYPE_OPTIONS, ...board.items.map((item) => item.itemType).filter(Boolean)])];
347
- const statusOptions = [...new Set([...TOMATO_STATUS_ORDER, ...board.items.map((item) => item.status).filter((status) => !TOMATO_STATUS_ORDER.includes(status))])];
348
- const statuses = [...new Set([...TOMATO_STATUS_ORDER.filter((status) => filteredItems.some((item) => item.status === status)), ...filteredItems.map((item) => item.status).filter((status) => !TOMATO_STATUS_ORDER.includes(status))])];
1176
+ const typeOptions = [...new Set(board.items.map((item) => item.itemType).filter(Boolean))].sort((left, right) => left.localeCompare(right));
1177
+ const statusOptions = applyLaneOrder([...new Set([...TOMATO_STATUS_ORDER, ...board.items.map((item) => item.status).filter((status) => !TOMATO_STATUS_ORDER.includes(status))])], laneOrder);
1178
+ const workspaceOptions = (() => {
1179
+ const entries = /* @__PURE__ */ new Map();
1180
+ for (const item of board.items) {
1181
+ const value = item.workspaceKey || item.workspaceName || item.workspace;
1182
+ if (!value) continue;
1183
+ const label = item.workspaceName && item.workspaceKey && item.workspaceName !== item.workspaceKey ? `${item.workspaceName} (${item.workspaceKey})` : value;
1184
+ if (!entries.has(value)) entries.set(value, {
1185
+ value,
1186
+ label
1187
+ });
1188
+ }
1189
+ return [...entries.values()];
1190
+ })();
1191
+ const defaultStatuses = [...new Set([...TOMATO_STATUS_ORDER.filter((status) => filteredItems.some((item) => item.status === status)), ...filteredItems.map((item) => item.status).filter((status) => !TOMATO_STATUS_ORDER.includes(status))])];
1192
+ const statuses = applyLaneOrder(defaultStatuses, laneOrder);
1193
+ const persistBlacklist = (next) => {
1194
+ window.localStorage.setItem(TOMATO_FILTER_BLACKLIST_KEY, JSON.stringify({
1195
+ types: [...next.types],
1196
+ statuses: [...next.statuses],
1197
+ workspaces: [...next.workspaces]
1198
+ }));
1199
+ return next;
1200
+ };
349
1201
  const toggleBlacklist = (kind, value) => setBlacklist((current) => {
350
1202
  const nextValues = new Set(current[kind]);
351
1203
  if (nextValues.has(value)) nextValues.delete(value);
352
1204
  else nextValues.add(value);
353
- const next = {
1205
+ return persistBlacklist({
354
1206
  ...current,
355
1207
  [kind]: nextValues
356
- };
357
- window.localStorage.setItem(TOMATO_FILTER_BLACKLIST_KEY, JSON.stringify({
358
- types: [...next.types],
359
- statuses: [...next.statuses]
360
- }));
361
- return next;
1208
+ });
362
1209
  });
1210
+ const setWorkspaceFilterAll = (visible) => setBlacklist((current) => persistBlacklist({
1211
+ ...current,
1212
+ workspaces: visible ? /* @__PURE__ */ new Set() : new Set(workspaceOptions.map((option) => option.value))
1213
+ }));
363
1214
  const toggleMutedItem = (itemKey) => setMutedItems((current) => {
364
1215
  const next = new Set(current);
365
1216
  if (next.has(itemKey)) next.delete(itemKey);
@@ -367,6 +1218,25 @@ window.__ModuleLoader__.load({
367
1218
  window.localStorage.setItem(TOMATO_MUTED_ITEMS_KEY, JSON.stringify([...next]));
368
1219
  return next;
369
1220
  });
1221
+ const moveLane = (dragged, target, after) => {
1222
+ if (dragged === target) return;
1223
+ previousLanePositions.current = new Map([...laneElements.current].map(([status, element]) => [status, element.getBoundingClientRect()]));
1224
+ setLaneOrder((current) => {
1225
+ const visible = applyLaneOrder(defaultStatuses, current).filter((status) => status !== dragged);
1226
+ const targetIndex = visible.indexOf(target);
1227
+ visible.splice(targetIndex + (after ? 1 : 0), 0, dragged);
1228
+ const previousTracked = laneOrderValues(current);
1229
+ const trackedSet = new Set([
1230
+ ...previousTracked,
1231
+ dragged,
1232
+ target
1233
+ ]);
1234
+ const hiddenTracked = previousTracked.filter((status) => !visible.includes(status));
1235
+ const next = createLaneOrder([...visible.filter((status) => trackedSet.has(status)), ...hiddenTracked]);
1236
+ window.localStorage.setItem(TOMATO_LANE_ORDER_KEY, JSON.stringify(next));
1237
+ return next;
1238
+ });
1239
+ };
370
1240
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
371
1241
  ref: workbenchRef,
372
1242
  className: tomato_board_module_css_default.workbench,
@@ -374,171 +1244,265 @@ window.__ModuleLoader__.load({
374
1244
  children: [
375
1245
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
376
1246
  className: tomato_board_module_css_default.header,
377
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: "番茄工作台" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: board.loading ? "正在读取番茄事项…" : `显示 ${filteredItems.length} / ${board.items.length} 个事项` })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
378
- className: tomato_board_module_css_default.actions,
379
- children: [
380
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
381
- className: tomato_board_module_css_default.searchField,
382
- children: [
383
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
384
- "aria-hidden": "true",
385
- children: "⌕"
1247
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1248
+ className: tomato_board_module_css_default.titleRow,
1249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: "番茄工作台" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1250
+ className: tomato_board_module_css_default.actions,
1251
+ children: [
1252
+ page === "points" && storyToolbar,
1253
+ page === "board" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1255
+ className: tomato_board_module_css_default.searchField,
1256
+ children: [
1257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1258
+ "aria-hidden": "true",
1259
+ children: "⌕"
1260
+ }),
1261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1262
+ type: "search",
1263
+ value: search,
1264
+ onChange: (event) => setSearch(event.target.value),
1265
+ placeholder: "搜索标题或 tag…",
1266
+ "aria-label": "搜索标题或 tag"
1267
+ }),
1268
+ search && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1269
+ type: "button",
1270
+ "aria-label": "清空搜索词",
1271
+ onClick: () => setSearch(""),
1272
+ children: "×"
1273
+ })
1274
+ ]
386
1275
  }),
387
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
388
- type: "search",
389
- value: search,
390
- onChange: (event) => setSearch(event.target.value),
391
- placeholder: "搜索标题或 tag…",
392
- "aria-label": "搜索标题或 tag"
1276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1277
+ ref: filterMenuRef,
1278
+ className: tomato_board_module_css_default.filterMenu,
1279
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
1280
+ "aria-label": "空间、负责人、类型和状态筛选",
1281
+ title: "空间、负责人、类型和状态筛选",
1282
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1283
+ "aria-hidden": "true",
1284
+ children: "▽"
1285
+ }), (blacklist.types.size > 0 || blacklist.statuses.size > 0 || blacklist.workspaces.size > 0 || selectedAssignee !== "currentUser()") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})]
1286
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1287
+ className: tomato_board_module_css_default.filterPopover,
1288
+ children: [
1289
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceFilterRow, {
1290
+ options: workspaceOptions,
1291
+ hidden: blacklist.workspaces,
1292
+ onToggle: (value) => toggleBlacklist("workspaces", value),
1293
+ onSetAll: setWorkspaceFilterAll
1294
+ }),
1295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssigneePicker, {
1296
+ users: filterDirectory.users,
1297
+ value: selectedAssignee,
1298
+ onChange: (value) => {
1299
+ setSelectedAssignee(value);
1300
+ refresh(value);
1301
+ }
1302
+ }),
1303
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterRow, {
1304
+ label: "类型",
1305
+ options: typeOptions,
1306
+ hidden: blacklist.types,
1307
+ onToggle: (value) => toggleBlacklist("types", value)
1308
+ }),
1309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterRow, {
1310
+ label: "状态",
1311
+ options: statusOptions,
1312
+ hidden: blacklist.statuses,
1313
+ onToggle: (value) => toggleBlacklist("statuses", value)
1314
+ })
1315
+ ]
1316
+ })]
393
1317
  }),
394
- search && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
395
- type: "button",
396
- "aria-label": "清空搜索词",
397
- onClick: () => setSearch(""),
398
- children: "×"
1318
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1319
+ variant: "toolbar",
1320
+ size: "sm",
1321
+ className: tomato_board_module_css_default.headerIconButton,
1322
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}),
1323
+ title: "刷新番茄事项",
1324
+ "aria-label": "刷新番茄事项",
1325
+ disabled: board.loading,
1326
+ onClick: () => {
1327
+ refresh(selectedAssignee);
1328
+ setDirectoryVersion((value) => value + 1);
1329
+ }
399
1330
  })
400
- ]
401
- }),
402
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
403
- className: tomato_board_module_css_default.filterMenu,
404
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
405
- "aria-label": "类型和状态筛选",
406
- title: "类型和状态筛选",
407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
408
- "aria-hidden": "true",
409
- children: "▽"
410
- }), (blacklist.types.size > 0 || blacklist.statuses.size > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})]
411
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
412
- className: tomato_board_module_css_default.filterPopover,
413
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterRow, {
414
- label: "类型",
415
- options: typeOptions,
416
- hidden: blacklist.types,
417
- onToggle: (value) => toggleBlacklist("types", value)
418
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterRow, {
419
- label: "状态",
420
- options: statusOptions,
421
- hidden: blacklist.statuses,
422
- onToggle: (value) => toggleBlacklist("statuses", value)
423
- })]
424
- })]
425
- }),
426
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
427
- variant: "toolbar",
428
- size: "sm",
429
- className: tomato_board_module_css_default.headerIconButton,
430
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}),
431
- title: "刷新番茄事项",
432
- "aria-label": "刷新番茄事项",
433
- disabled: board.loading,
434
- onClick: () => void refresh()
435
- }),
436
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
437
- variant: "toolbar",
438
- size: "sm",
439
- className: tomato_board_module_css_default.headerIconButton,
440
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {}),
441
- title: "关闭番茄工作台",
442
- "aria-label": "关闭番茄工作台",
443
- onClick: closeWorkbench
444
- })
445
- ]
1331
+ ] }),
1332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1333
+ variant: "toolbar",
1334
+ size: "sm",
1335
+ className: tomato_board_module_css_default.headerIconButton,
1336
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {}),
1337
+ title: "关闭番茄工作台",
1338
+ "aria-label": "关闭番茄工作台",
1339
+ onClick: closeWorkbench
1340
+ })
1341
+ ]
1342
+ })]
1343
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
1344
+ className: tomato_board_module_css_default.pageTabs,
1345
+ "aria-label": "番茄工作台页面",
1346
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1347
+ type: "button",
1348
+ "aria-current": page === "board" ? "page" : void 0,
1349
+ onClick: () => setPage("board"),
1350
+ children: "事项看板"
1351
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1352
+ type: "button",
1353
+ "aria-current": page === "points" ? "page" : void 0,
1354
+ onClick: () => setPage("points"),
1355
+ children: "迭代投入"
1356
+ })]
446
1357
  })]
447
1358
  }),
448
- board.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
449
- className: tomato_board_module_css_default.error,
450
- role: "alert",
451
- children: board.error
452
- }),
453
- board.truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
454
- className: tomato_board_module_css_default.notice,
455
- role: "status",
456
- children: [
457
- "事项数量已达配置上限,当前仅展示前 ",
458
- board.items.length,
459
- " 条。"
460
- ]
461
- }),
462
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
463
- className: tomato_board_module_css_default.board,
464
- children: [statuses.map((status) => {
465
- const items = filteredItems.filter((item) => item.status === status);
466
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
467
- className: tomato_board_module_css_default.lane,
468
- "aria-labelledby": `tomato-lane-${status}`,
469
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
470
- className: tomato_board_module_css_default.laneHeader,
471
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
472
- id: `tomato-lane-${status}`,
473
- children: status
474
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: items.length })]
475
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
476
- className: tomato_board_module_css_default.cards,
477
- children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
478
- className: `${tomato_board_module_css_default.card} ${mutedItems.has(item.itemKey) ? tomato_board_module_css_default.cardMuted : ""}`,
479
- tabIndex: 0,
480
- role: "button",
481
- onClick: () => openItem(item),
482
- onKeyDown: (event) => {
483
- if (event.key === "Enter" || event.key === " ") {
484
- event.preventDefault();
485
- openItem(item);
1359
+ page === "points" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StoryPoints, {
1360
+ toolbarTarget: setStoryToolbar,
1361
+ onOpenItem: openItem
1362
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1363
+ board.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1364
+ className: tomato_board_module_css_default.error,
1365
+ role: "alert",
1366
+ children: board.error
1367
+ }),
1368
+ board.truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1369
+ className: tomato_board_module_css_default.notice,
1370
+ role: "status",
1371
+ children: [
1372
+ "事项数量已达配置上限,当前仅展示前 ",
1373
+ board.items.length,
1374
+ " 条。"
1375
+ ]
1376
+ }),
1377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1378
+ className: tomato_board_module_css_default.board,
1379
+ children: [statuses.map((status) => {
1380
+ const items = filteredItems.filter((item) => item.status === status);
1381
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1382
+ ref: (element) => {
1383
+ if (element) laneElements.current.set(status, element);
1384
+ else laneElements.current.delete(status);
1385
+ },
1386
+ className: `${tomato_board_module_css_default.lane} ${draggedLane === status ? tomato_board_module_css_default.laneDragging : ""} ${dropLane?.status === status ? dropLane.after ? tomato_board_module_css_default.laneDropAfter : tomato_board_module_css_default.laneDropBefore : ""}`,
1387
+ "aria-labelledby": `tomato-lane-${status}`,
1388
+ onDragOver: (event) => {
1389
+ if (!draggedLane || draggedLane === status) return;
1390
+ event.preventDefault();
1391
+ const bounds = event.currentTarget.getBoundingClientRect();
1392
+ setDropLane({
1393
+ status,
1394
+ after: event.clientX >= bounds.left + bounds.width / 2
1395
+ });
1396
+ },
1397
+ onDrop: (event) => {
1398
+ event.preventDefault();
1399
+ if (draggedLane && dropLane?.status === status) moveLane(draggedLane, status, dropLane.after);
1400
+ setDraggedLane(null);
1401
+ setDropLane(null);
1402
+ },
1403
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1404
+ className: tomato_board_module_css_default.laneHeader,
1405
+ draggable: true,
1406
+ title: "拖拽调整泳道顺序",
1407
+ onDragStart: (event) => {
1408
+ event.dataTransfer.effectAllowed = "move";
1409
+ event.dataTransfer.setData("text/plain", status);
1410
+ const lane = event.currentTarget.parentElement;
1411
+ if (lane) {
1412
+ const bounds = lane.getBoundingClientRect();
1413
+ event.dataTransfer.setDragImage(lane, Math.min(Math.max(event.clientX - bounds.left, 0), bounds.width), Math.min(Math.max(event.clientY - bounds.top, 0), bounds.height));
486
1414
  }
1415
+ setDraggedLane(status);
487
1416
  },
488
- children: [
489
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
490
- className: tomato_board_module_css_default.cardTopline,
491
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
492
- className: tomato_board_module_css_default.key,
493
- children: item.itemKey
494
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
495
- className: tomato_board_module_css_default.cardButtons,
496
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
497
- className: tomato_board_module_css_default.muteButton,
498
- variant: "ghost",
499
- size: "sm",
500
- title: mutedItems.has(item.itemKey) ? "取消置灰" : "置灰标记",
501
- "aria-label": mutedItems.has(item.itemKey) ? `取消置灰 ${item.itemKey}` : `置灰 ${item.itemKey}`,
502
- "aria-pressed": mutedItems.has(item.itemKey),
503
- onClick: (event) => {
504
- event.stopPropagation();
505
- toggleMutedItem(item.itemKey);
506
- },
507
- onKeyDown: (event) => event.stopPropagation(),
508
- children: "●"
509
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
510
- className: tomato_board_module_css_default.tomatoLink,
511
- variant: "ghost",
512
- size: "sm",
513
- title: "在番茄中打开事项",
514
- "aria-label": `在番茄中打开 ${item.itemKey}`,
515
- onClick: (event) => {
516
- event.stopPropagation();
517
- window.open(item.tomatoUrl, "_blank", "noopener,noreferrer");
518
- },
519
- onKeyDown: (event) => event.stopPropagation(),
520
- children: "↗"
1417
+ onDragEnd: () => {
1418
+ setDraggedLane(null);
1419
+ setDropLane(null);
1420
+ },
1421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1422
+ id: `tomato-lane-${status}`,
1423
+ children: status
1424
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: items.length })]
1425
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1426
+ className: tomato_board_module_css_default.cards,
1427
+ children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
1428
+ className: `${tomato_board_module_css_default.card} ${mutedItems.has(item.itemKey) ? tomato_board_module_css_default.cardMuted : ""}`,
1429
+ tabIndex: 0,
1430
+ role: "button",
1431
+ onClick: () => openItem(item),
1432
+ onKeyDown: (event) => {
1433
+ if (event.key === "Enter" || event.key === " ") {
1434
+ event.preventDefault();
1435
+ openItem(item);
1436
+ }
1437
+ },
1438
+ children: [
1439
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1440
+ className: tomato_board_module_css_default.cardTopline,
1441
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1442
+ className: tomato_board_module_css_default.key,
1443
+ children: item.itemKey
1444
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1445
+ className: tomato_board_module_css_default.cardButtons,
1446
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1447
+ className: tomato_board_module_css_default.muteButton,
1448
+ variant: "ghost",
1449
+ size: "sm",
1450
+ title: mutedItems.has(item.itemKey) ? "取消置灰" : "置灰标记",
1451
+ "aria-label": mutedItems.has(item.itemKey) ? `取消置灰 ${item.itemKey}` : `置灰 ${item.itemKey}`,
1452
+ "aria-pressed": mutedItems.has(item.itemKey),
1453
+ onClick: (event) => {
1454
+ event.stopPropagation();
1455
+ toggleMutedItem(item.itemKey);
1456
+ },
1457
+ onKeyDown: (event) => event.stopPropagation(),
1458
+ children: "●"
1459
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1460
+ className: tomato_board_module_css_default.tomatoLink,
1461
+ variant: "ghost",
1462
+ size: "sm",
1463
+ title: "在番茄中打开事项",
1464
+ "aria-label": `在番茄中打开 ${item.itemKey}`,
1465
+ onClick: (event) => {
1466
+ event.stopPropagation();
1467
+ window.open(item.tomatoUrl, "_blank", "noopener,noreferrer");
1468
+ },
1469
+ onKeyDown: (event) => event.stopPropagation(),
1470
+ children: "↗"
1471
+ })]
521
1472
  })]
522
- })]
523
- }),
524
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.title }),
525
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
526
- className: tomato_board_module_css_default.meta,
527
- children: [
528
- item.itemType,
529
- item.priority,
530
- item.creator
531
- ].filter(Boolean).join(" · ")
532
- })
533
- ]
534
- }, item.itemKey))
535
- })]
536
- }, status);
537
- }), !board.loading && !board.error && statuses.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
538
- className: tomato_board_module_css_default.empty,
539
- children: "当前没有可显示的番茄事项"
540
- })]
541
- }),
1473
+ }),
1474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.title }),
1475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1476
+ className: tomato_board_module_css_default.cardMeta,
1477
+ children: [
1478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, {
1479
+ tone: typeTone(item.itemType),
1480
+ children: item.itemType
1481
+ }),
1482
+ (item.workspaceName || item.workspaceKey) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, {
1483
+ title: "空间",
1484
+ children: item.workspaceName && item.workspaceKey && item.workspaceName !== item.workspaceKey ? `${item.workspaceName} (${item.workspaceKey})` : item.workspaceKey || item.workspaceName
1485
+ }),
1486
+ item.priority && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, {
1487
+ title: "优先级",
1488
+ children: priorityLabel(item.priority)
1489
+ }),
1490
+ item.creator && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tag, {
1491
+ title: "创建人",
1492
+ children: ["创建 ", item.creator]
1493
+ })
1494
+ ]
1495
+ })
1496
+ ]
1497
+ }, item.itemKey))
1498
+ })]
1499
+ }, status);
1500
+ }), !board.loading && !board.error && statuses.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1501
+ className: tomato_board_module_css_default.empty,
1502
+ children: "当前没有可显示的番茄事项"
1503
+ })]
1504
+ })
1505
+ ] }),
542
1506
  board.selectedItem ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateConversationDialog, {
543
1507
  ctx,
544
1508
  item: board.selectedItem
@@ -546,18 +1510,180 @@ window.__ModuleLoader__.load({
546
1510
  ]
547
1511
  });
548
1512
  }
1513
+ const PRIORITY_LABELS = {
1514
+ "69e65065-4b34-4109-bca9-0154e548554a": "P0",
1515
+ "8f7912a5-9176-4a79-a269-2269ac42b5a2": "P1",
1516
+ "ca8c3e43-3e7b-444d-8940-d0967d944921": "P2",
1517
+ "ec31e4c1-b55b-479d-be97-86d5f7bf38ef": "P2",
1518
+ "1a3e1092-7d70-42ee-ad38-0e8d953c4c23": "P3",
1519
+ "faae52da-28c8-46fc-96dd-db9cdb28b557": "P4"
1520
+ };
1521
+ const priorityLabel = (value) => PRIORITY_LABELS[value] ?? value;
1522
+ const TYPE_TONES = {
1523
+ Story: "#2f7d72",
1524
+ EnablerStory: "#2777a8",
1525
+ Task: "#7b61a8",
1526
+ Bug: "#c34f43",
1527
+ 缺陷: "#c34f43",
1528
+ 测试缺陷: "#d06438",
1529
+ Epic: "#9a6b24",
1530
+ Feature: "#3f68ad"
1531
+ };
1532
+ function typeTone(type) {
1533
+ if (TYPE_TONES[type]) return TYPE_TONES[type];
1534
+ const palette = [
1535
+ "#2f7d72",
1536
+ "#2777a8",
1537
+ "#7b61a8",
1538
+ "#c34f43",
1539
+ "#9a6b24",
1540
+ "#51753a",
1541
+ "#a14f78"
1542
+ ];
1543
+ return palette[[...type].reduce((hash, char) => hash + char.charCodeAt(0), 0) % palette.length];
1544
+ }
549
1545
  function FilterRow({ label, options, hidden, onToggle }) {
550
1546
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
551
1547
  className: tomato_board_module_css_default.filterRow,
552
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
553
- className: hidden.has(option) ? "" : tomato_board_module_css_default.selectedFilter,
554
- type: "button",
555
- "aria-pressed": !hidden.has(option),
1548
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, {
1549
+ interactive: true,
1550
+ pressed: !hidden.has(option),
1551
+ tone: label === "类型" ? typeTone(option) : void 0,
556
1552
  onClick: () => onToggle(option),
557
1553
  children: option
558
1554
  }, option)) })]
559
1555
  });
560
1556
  }
1557
+ const WORKSPACE_SELECT_ALL = "workspace:select-all";
1558
+ const WORKSPACE_SELECT_NONE = "workspace:select-none";
1559
+ /** Multi-select workspace dropdown composed on the shared Menu primitive. */
1560
+ function WorkspaceFilterRow({ options, hidden, onToggle, onSetAll }) {
1561
+ const [open, setOpen] = (0, react.useState)(false);
1562
+ const rootRef = (0, react.useRef)(null);
1563
+ const visibleValues = options.filter((option) => !hidden.has(option.value)).map((option) => option.value);
1564
+ const summary = options.length === 0 ? "暂无空间" : hidden.size === 0 ? `全部空间 · ${options.length}` : visibleValues.length === 0 ? "未选择空间" : `已选空间 ${visibleValues.length} / ${options.length}`;
1565
+ (0, react.useEffect)(() => {
1566
+ if (!open) return;
1567
+ const onKey = (event) => {
1568
+ if (event.key === "Escape") {
1569
+ event.stopPropagation();
1570
+ setOpen(false);
1571
+ }
1572
+ };
1573
+ const root = rootRef.current;
1574
+ root?.addEventListener("keydown", onKey, true);
1575
+ return () => root?.removeEventListener("keydown", onKey, true);
1576
+ }, [open]);
1577
+ const items = options.map((option) => ({
1578
+ id: option.value,
1579
+ label: option.label
1580
+ }));
1581
+ const footer = [{
1582
+ id: WORKSPACE_SELECT_ALL,
1583
+ label: "全部显示"
1584
+ }, {
1585
+ id: WORKSPACE_SELECT_NONE,
1586
+ label: "全部隐藏"
1587
+ }];
1588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1589
+ className: tomato_board_module_css_default.filterRow,
1590
+ ref: rootRef,
1591
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "空间" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1592
+ className: tomato_board_module_css_default.workspaceSelect,
1593
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1594
+ open,
1595
+ items,
1596
+ footer,
1597
+ selectedIds: visibleValues,
1598
+ onSelect: (id) => {
1599
+ if (id === WORKSPACE_SELECT_ALL) onSetAll(true);
1600
+ else if (id === WORKSPACE_SELECT_NONE) onSetAll(false);
1601
+ else onToggle(id);
1602
+ },
1603
+ onClose: () => {
1604
+ setOpen(false);
1605
+ },
1606
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1607
+ type: "button",
1608
+ className: tomato_board_module_css_default.workspaceTrigger,
1609
+ "aria-expanded": open,
1610
+ title: summary,
1611
+ onClick: () => {
1612
+ setOpen(!open);
1613
+ },
1614
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: summary }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
1615
+ className: tomato_board_module_css_default.workspaceChevron,
1616
+ "aria-hidden": true,
1617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
1618
+ })]
1619
+ })
1620
+ })
1621
+ })]
1622
+ });
1623
+ }
1624
+ function AssigneePicker({ users, value, onChange }) {
1625
+ const rootRef = (0, react.useRef)(null);
1626
+ const [open, setOpen] = (0, react.useState)(false);
1627
+ const [query, setQuery] = (0, react.useState)("");
1628
+ const options = (0, react.useMemo)(() => [{
1629
+ username: "currentUser()",
1630
+ name: "我负责的"
1631
+ }, ...users.filter((user) => user.username !== "currentUser()")], [users]);
1632
+ const selected = options.find((option) => option.username === value) ?? options[0];
1633
+ const needle = query.trim().toLowerCase();
1634
+ const filtered = needle ? options.filter((option) => `${option.name} ${option.username}`.toLowerCase().includes(needle)) : options;
1635
+ (0, react.useEffect)(() => {
1636
+ if (!open) return;
1637
+ const close = (event) => {
1638
+ if (event.target instanceof Node && !rootRef.current?.contains(event.target)) setOpen(false);
1639
+ };
1640
+ document.addEventListener("pointerdown", close, true);
1641
+ return () => document.removeEventListener("pointerdown", close, true);
1642
+ }, [open]);
1643
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1644
+ className: tomato_board_module_css_default.assigneeFilter,
1645
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "负责人" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1646
+ ref: rootRef,
1647
+ className: tomato_board_module_css_default.assigneePicker,
1648
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1649
+ variant: "outline",
1650
+ size: "sm",
1651
+ className: tomato_board_module_css_default.assigneeTrigger,
1652
+ "aria-haspopup": "listbox",
1653
+ "aria-expanded": open,
1654
+ onClick: () => {
1655
+ setOpen((current) => !current);
1656
+ setQuery("");
1657
+ },
1658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selected?.name ?? value }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})]
1659
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1660
+ className: tomato_board_module_css_default.assigneeDropdown,
1661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1662
+ autoFocus: true,
1663
+ type: "search",
1664
+ value: query,
1665
+ onChange: (event) => setQuery(event.target.value),
1666
+ placeholder: "搜索昵称或用户名…",
1667
+ "aria-label": "搜索负责人"
1668
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1669
+ role: "listbox",
1670
+ "aria-label": "负责人",
1671
+ children: [filtered.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1672
+ type: "button",
1673
+ role: "option",
1674
+ "aria-selected": option.username === value,
1675
+ onClick: () => {
1676
+ onChange(option.username);
1677
+ setOpen(false);
1678
+ setQuery("");
1679
+ },
1680
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: option.name }), option.name !== option.username && option.username !== "currentUser()" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: option.username }) : null]
1681
+ }, option.username)), filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "没有匹配的负责人" }) : null]
1682
+ })]
1683
+ }) : null]
1684
+ })]
1685
+ });
1686
+ }
561
1687
  function TomatoConversationShortcut({ ctx, sessionId, useSessions }) {
562
1688
  const itemKey = useSessions((state) => {
563
1689
  const summary = state.byId[sessionId];
@@ -719,6 +1845,14 @@ window.__ModuleLoader__.load({
719
1845
  "workspaces"
720
1846
  ];
721
1847
  function apply(ctx) {
1848
+ const openWorkbench = () => {
1849
+ if (disposeWorkbench) return;
1850
+ emit({ open: true });
1851
+ disposeWorkbench = ctx.slots.register({
1852
+ name: "conversation",
1853
+ priority: -100
1854
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TomatoBoardPanel, { ctx }));
1855
+ };
722
1856
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
723
1857
  name: "conversation.session.header.actions",
724
1858
  id: "tomato-shortcut",
@@ -732,15 +1866,13 @@ window.__ModuleLoader__.load({
732
1866
  id: "tomato-board"
733
1867
  }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TomatoBoardAction, {
734
1868
  ...props,
735
- openWorkbench: () => {
736
- if (disposeWorkbench) return;
737
- emit({ open: true });
738
- disposeWorkbench = ctx.slots.register({
739
- name: "conversation",
740
- priority: -100
741
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TomatoBoardPanel, { ctx }));
742
- }
1869
+ openWorkbench
743
1870
  })));
1871
+ ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
1872
+ name: "conversation.session.header.actions",
1873
+ id: "tomato-board-topbar",
1874
+ order: 11
1875
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TomatoBoardTopbarAction, { openWorkbench })));
744
1876
  }
745
1877
  //#endregion
746
1878
  exports.apply = apply;