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