@xirang-ai/dsh-teamflow 0.1.0 → 0.1.2

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
@@ -1,4 +1,4 @@
1
- /* @xirang-ai/dsh-teamflow 0.1.0 — generated by scripts/build.mjs, do not edit. */
1
+ /* @xirang-ai/dsh-teamflow 0.1.2 — generated by scripts/build.mjs, do not edit. */
2
2
  window.__ModuleLoader__.load({
3
3
  id: "@xirang-ai/dsh-teamflow",
4
4
  factory: (require) => {
@@ -234,10 +234,12 @@ window.__ModuleLoader__.load({
234
234
  "composer.locked": "The conversation has started; create a new one to switch agents.",
235
235
  "composer.noPreset": "The preset agent is not loaded, so the switch was not applied.",
236
236
  "composer.other": "Other (default)",
237
- "create.attachment.add": "Add files",
237
+ "create.attachment.add": "Add attachments",
238
238
  "create.attachment.blocked": "Attachments are still uploading; wait for them to finish before sending.",
239
239
  "create.attachment.busy": "The conversation is busy, so the attachments were not sent. Try again.",
240
240
  "create.attachment.cancelled": "Upload cancelled; nothing was sent.",
241
+ "create.attachment.dropActive": "Release to attach these files.",
242
+ "create.attachment.dropHint": "Click to choose files, or drop them here.",
241
243
  "create.attachment.failed": "Upload failed",
242
244
  "create.attachment.failed.network": "The upload failed. Check the network and try again.",
243
245
  "create.attachment.failed.rejected": "The host rejected the file (damaged content or wrong type).",
@@ -339,6 +341,7 @@ window.__ModuleLoader__.load({
339
341
  "delete.statusMissing": "—",
340
342
  "delete.summary": "{total} record(s), {deletable} deletable",
341
343
  "detail.branch": "Branch",
344
+ "detail.bugDiscussion": "Discuss this bug in a new session",
342
345
  "detail.bugBody": "What happened",
343
346
  "detail.completedAt": "Completed",
344
347
  "detail.description": "Description",
@@ -354,6 +357,7 @@ window.__ModuleLoader__.load({
354
357
  "detail.discussionNewDetail": "Discuss the detailed design in a new conversation",
355
358
  "detail.discussionNewSplit": "Split Stories in a new conversation",
356
359
  "detail.discussionOpen": "Open",
360
+ "detail.storyDiscussion": "Discuss this story in a new session",
357
361
  "detail.discussionStarting": "Creating conversation…",
358
362
  "detail.docs": "Detailed design",
359
363
  "detail.docsEmpty": "No detailed design written yet.",
@@ -570,10 +574,12 @@ window.__ModuleLoader__.load({
570
574
  "composer.locked": "会话已开始,无法再切换 Agent;请新建会话。",
571
575
  "composer.noPreset": "预设 Agent 未加载,无法切换。",
572
576
  "composer.other": "其他(默认)",
573
- "create.attachment.add": "选择文件",
577
+ "create.attachment.add": "添加附件",
574
578
  "create.attachment.blocked": "附件正在上传,上传完成后再提交。",
575
579
  "create.attachment.busy": "会话正忙,附件未发送,请稍后重试。",
576
580
  "create.attachment.cancelled": "已取消上传,附件未发送。",
581
+ "create.attachment.dropActive": "松手即可添加附件。",
582
+ "create.attachment.dropHint": "点击选择文件,或把文件拖到这里。",
577
583
  "create.attachment.failed": "上传失败",
578
584
  "create.attachment.failed.network": "上传暂时失败,请检查网络后重试。",
579
585
  "create.attachment.failed.rejected": "宿主拒绝了该文件(内容损坏或类型不符)。",
@@ -675,6 +681,7 @@ window.__ModuleLoader__.load({
675
681
  "delete.statusMissing": "—",
676
682
  "delete.summary": "共 {total} 条,可删除 {deletable} 条",
677
683
  "detail.branch": "分支",
684
+ "detail.bugDiscussion": "在新会话中进行缺陷讨论",
678
685
  "detail.bugBody": "缺陷说明",
679
686
  "detail.completedAt": "完成时间",
680
687
  "detail.description": "故事说明",
@@ -690,6 +697,7 @@ window.__ModuleLoader__.load({
690
697
  "detail.discussionNewDetail": "在新会话中进行详细设计讨论",
691
698
  "detail.discussionNewSplit": "在新会话中进行故事拆分",
692
699
  "detail.discussionOpen": "打开",
700
+ "detail.storyDiscussion": "在新会话中进行故事讨论",
693
701
  "detail.discussionStarting": "正在创建会话…",
694
702
  "detail.docs": "详细设计",
695
703
  "detail.docsEmpty": "还没有写详细设计。",
@@ -893,6 +901,7 @@ window.__ModuleLoader__.load({
893
901
  cancelStory: "cancelStory",
894
902
  completeBug: "completeBug",
895
903
  prepareRequirementSession: "prepareRequirementSession",
904
+ prepareDiscussionSession: "prepareDiscussionSession",
896
905
  sessionPermissionTier: "sessionPermissionTier",
897
906
  previewDelete: "previewDelete",
898
907
  deleteRecords: "deleteRecords",
@@ -1030,6 +1039,21 @@ window.__ModuleLoader__.load({
1030
1039
  ]),
1031
1040
  result: JSON_CODEC
1032
1041
  }),
1042
+ Object.freeze({
1043
+ id: `${PACKAGE_NAME}#${METHODS.prepareDiscussionSession}`,
1044
+ service: SERVICE_KEY,
1045
+ namespace: NAMESPACE,
1046
+ method: METHODS.prepareDiscussionSession,
1047
+ invocation: { kind: "direct" },
1048
+ parameters: Object.freeze([
1049
+ parameter("subjectType", "Discussion subject: requirement, story or bug"),
1050
+ parameter("subjectId", "Requirement, story or defect the discussion belongs to"),
1051
+ parameter("sessionId", "Newly created conversation to link"),
1052
+ parameter("kind", "Discussion kind: discuss for stories and defects, the requirement kinds otherwise"),
1053
+ LOCALE_PARAMETER
1054
+ ]),
1055
+ result: JSON_CODEC
1056
+ }),
1033
1057
  Object.freeze({
1034
1058
  id: `${PACKAGE_NAME}#${METHODS.sessionPermissionTier}`,
1035
1059
  service: SERVICE_KEY,
@@ -1093,6 +1117,7 @@ window.__ModuleLoader__.load({
1093
1117
  });
1094
1118
 
1095
1119
  // src/client/composer.tsx
1120
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1096
1121
  var import_react = require("react");
1097
1122
 
1098
1123
  // src/create.ts
@@ -1146,6 +1171,15 @@ window.__ModuleLoader__.load({
1146
1171
  }
1147
1172
  return ["【继续讨论需求与概要设计】", "", "我的补充:"].join("\n");
1148
1173
  }
1174
+ var STORY_DISCUSSION_PRESET = "xirang-story";
1175
+ var BUG_DISCUSSION_PRESET = "xirang-bug";
1176
+ function presetForRecordDiscussion(kind) {
1177
+ return kind === "bug" ? BUG_DISCUSSION_PRESET : STORY_DISCUSSION_PRESET;
1178
+ }
1179
+ function buildRecordDiscussionPrompt(kind, title) {
1180
+ const heading = kind === "bug" ? "【继续讨论缺陷】" : "【继续讨论故事】";
1181
+ return [heading, title, "", "我的补充:"].join("\n");
1182
+ }
1149
1183
 
1150
1184
  // src/client/composer-kind.ts
1151
1185
  var COMPOSER_KINDS = [...CREATE_KINDS, "design", "detail", "split"];
@@ -2281,8 +2315,82 @@ window.__ModuleLoader__.load({
2281
2315
  * Attachment block. The picker is the Host's own file input, so it keeps the
2282
2316
  * Host rules for what can be selected; it is only hidden because the visible
2283
2317
  * control is the card's button and native inputs cannot be styled consistently.
2318
+ * The whole card body is one picker: a large plus, the action, and what a drop
2319
+ * will do. It stays a real <button>, so the drop target always keeps a
2320
+ * keyboard and single-pointer equivalent for the drag gesture.
2321
+ */
2322
+ .tf-attachment-pick {
2323
+ display: flex;
2324
+ flex-direction: column;
2325
+ align-items: center;
2326
+ justify-content: center;
2327
+ gap: 6px;
2328
+ width: 100%;
2329
+ min-height: 150px;
2330
+ margin-top: 10px;
2331
+ padding: 20px 16px;
2332
+ border: 1px dashed var(--dsw-alias-border-l3, currentColor);
2333
+ border-radius: 12px;
2334
+ background: var(--dsw-alias-bg-l2, transparent);
2335
+ color: var(--dsw-alias-label-secondary, inherit);
2336
+ font: inherit;
2337
+ text-align: center;
2338
+ cursor: pointer;
2339
+ transition: border-color .12s ease, background-color .12s ease;
2340
+ }
2341
+ .tf-attachment-pick:hover:not(:disabled) {
2342
+ border-color: var(--dsw-alias-state-business-primary, #4176e6);
2343
+ background: var(--dsw-alias-interactive-bg-hover, transparent);
2344
+ }
2345
+ .tf-attachment-pick:focus-visible {
2346
+ outline: 2px solid var(--dsw-alias-link, #4176e6);
2347
+ outline-offset: 2px;
2348
+ }
2349
+ .tf-attachment-pick:disabled { opacity: .5; cursor: default; }
2350
+ .tf-attachment-plus {
2351
+ display: grid;
2352
+ place-items: center;
2353
+ width: 40px;
2354
+ height: 40px;
2355
+ border-radius: 50%;
2356
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .04));
2357
+ color: var(--dsw-alias-state-business-primary, #4176e6);
2358
+ font-size: 24px;
2359
+ font-weight: 300;
2360
+ line-height: 1;
2361
+ }
2362
+ .tf-attachment-pick-title {
2363
+ font-size: 13px;
2364
+ font-weight: 600;
2365
+ color: var(--dsw-alias-label-primary, inherit);
2366
+ }
2367
+ .tf-attachment-pick-sub {
2368
+ font-size: 11.5px;
2369
+ line-height: 1.5;
2370
+ color: var(--dsw-alias-label-tertiary, inherit);
2371
+ }
2372
+ .tf-attachment-limits {
2373
+ margin: 8px 0 0;
2374
+ font-size: 11.5px;
2375
+ line-height: 1.5;
2376
+ text-align: center;
2377
+ color: var(--dsw-alias-label-caption, inherit);
2378
+ }
2379
+ /*
2380
+ * The attachment card doubles as the drop target, so a file dropped anywhere
2381
+ * the user reads as "the attachment area" lands on the picker's own intake path.
2382
+ * Only the picker itself lights up while a file drag is inside the card, and its
2383
+ * subtitle says what releasing will do.
2284
2384
  */
2285
- .tf-attachment-bar { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
2385
+ .tf-attachment-dropzone[data-drag='active'] .tf-attachment-pick {
2386
+ border-style: solid;
2387
+ border-color: var(--dsw-alias-state-business-primary, #4176e6);
2388
+ background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 7%, transparent);
2389
+ }
2390
+ .tf-attachment-dropzone[data-drag='active'] .tf-attachment-pick-sub {
2391
+ color: var(--dsw-alias-state-business-primary, #4176e6);
2392
+ font-weight: 600;
2393
+ }
2286
2394
  .tf-attachment-input {
2287
2395
  position: absolute;
2288
2396
  width: 1px;
@@ -2472,23 +2580,37 @@ window.__ModuleLoader__.load({
2472
2580
  `;
2473
2581
  var COMPOSER_STYLES = `
2474
2582
  .tf-composer { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
2475
- .tf-composer-field { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
2476
- .tf-composer-label { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, inherit); white-space: nowrap; }
2477
- .tf-composer-select {
2478
- height: 24px;
2479
- max-width: 132px;
2480
- padding: 0 6px;
2481
- border: 1px solid var(--dsw-alias-border-l2, transparent);
2482
- border-radius: 7px;
2483
- background: transparent;
2583
+ /* Trigger matches the Host's composer selects (permission, model): same height,
2584
+ radius, spacing, type scale and interaction colors, all from theme tokens. */
2585
+ .tf-composer-trigger {
2586
+ display: inline-flex;
2587
+ align-items: center;
2588
+ gap: 4px;
2589
+ min-width: 0;
2590
+ max-width: 220px;
2591
+ height: 28px;
2592
+ padding: 0 4px 0 8px;
2593
+ border: none;
2594
+ border-radius: var(--dsw-radius-sm, 7px);
2595
+ background: none;
2484
2596
  color: var(--dsw-alias-label-secondary, inherit);
2485
2597
  font: inherit;
2486
- font-size: 11.5px;
2598
+ font-size: 13px;
2599
+ font-weight: 500;
2600
+ line-height: 20px;
2487
2601
  cursor: pointer;
2488
2602
  outline: none;
2489
2603
  }
2490
- .tf-composer-select:focus { border-color: var(--dsw-alias-label-caption, transparent); }
2491
- .tf-composer-select:disabled { opacity: .55; cursor: default; }
2604
+ @media (hover: hover) {
2605
+ .tf-composer-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, transparent); }
2606
+ }
2607
+ .tf-composer-trigger:focus-visible {
2608
+ box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
2609
+ }
2610
+ .tf-composer-trigger:disabled { color: var(--dsw-alias-label-dimmed, inherit); cursor: default; }
2611
+ .tf-composer-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2612
+ .tf-composer-chevron { display: inline-flex; flex: none; color: var(--dsw-alias-label-caption, inherit); transition: transform .12s; }
2613
+ .tf-composer-chevron-open { transform: rotate(180deg); }
2492
2614
  .tf-composer-note {
2493
2615
  font-size: 11px;
2494
2616
  color: var(--dsw-alias-label-caption, inherit);
@@ -2516,6 +2638,7 @@ window.__ModuleLoader__.load({
2516
2638
  const kinds = discussionSession ? COMPOSER_KINDS : CREATE_KINDS;
2517
2639
  const [pending, setPending] = (0, import_react.useState)(false);
2518
2640
  const [error, setError] = (0, import_react.useState)(null);
2641
+ const [open, setOpen] = (0, import_react.useState)(false);
2519
2642
  const change = (0, import_react.useCallback)(
2520
2643
  (next) => {
2521
2644
  setError(null);
@@ -2569,26 +2692,55 @@ window.__ModuleLoader__.load({
2569
2692
  },
2570
2693
  [agentPresets, blank, mode, sessionId, t]
2571
2694
  );
2695
+ const locked = !blank;
2696
+ const currentLabel = mode === "" ? t("composer.other") : t(COMPOSER_KIND_LABEL[mode]);
2697
+ const items = [
2698
+ { id: "", label: t("composer.other") },
2699
+ ...kinds.map((kind) => ({ id: kind, label: t(COMPOSER_KIND_LABEL[kind]) }))
2700
+ ];
2572
2701
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2573
2702
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: COMPOSER_STYLES }),
2574
2703
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tf-composer", children: [
2575
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "tf-composer-field", children: [
2576
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-label", children: t("composer.label") }),
2577
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2578
- "select",
2579
- {
2580
- className: "tf-select tf-composer-select",
2581
- value: mode,
2582
- disabled: pending,
2583
- title: blank ? t("composer.hint") : t("composer.locked"),
2584
- onChange: (event) => change(event.target.value),
2585
- children: [
2586
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("composer.other") }),
2587
- kinds.map((kind) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: kind, children: t(COMPOSER_KIND_LABEL[kind]) }, kind))
2588
- ]
2589
- }
2590
- )
2591
- ] }),
2704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2705
+ import_dsh_client_ui_primitives.Menu,
2706
+ {
2707
+ open,
2708
+ items,
2709
+ selectedId: mode,
2710
+ onSelect: (id2) => {
2711
+ setOpen(false);
2712
+ change(id2);
2713
+ },
2714
+ onClose: () => setOpen(false),
2715
+ side: "top",
2716
+ portal: true,
2717
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2718
+ "button",
2719
+ {
2720
+ type: "button",
2721
+ className: "tf-composer-trigger",
2722
+ "aria-label": `${t("composer.label")}: ${currentLabel}`,
2723
+ "aria-haspopup": "menu",
2724
+ "aria-expanded": open,
2725
+ "aria-busy": pending,
2726
+ title: blank ? t("composer.hint") : t("composer.locked"),
2727
+ disabled: pending || locked,
2728
+ onClick: () => setOpen((value2) => !value2),
2729
+ children: [
2730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-value", children: currentLabel }),
2731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2732
+ "span",
2733
+ {
2734
+ className: open ? "tf-composer-chevron tf-composer-chevron-open" : "tf-composer-chevron",
2735
+ "aria-hidden": "true",
2736
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
2737
+ }
2738
+ )
2739
+ ]
2740
+ }
2741
+ )
2742
+ }
2743
+ ),
2592
2744
  error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-note", role: "status", children: error }) : null
2593
2745
  ] })
2594
2746
  ] });
@@ -2700,6 +2852,12 @@ window.__ModuleLoader__.load({
2700
2852
  }
2701
2853
  return { accepted: kept, rejections };
2702
2854
  }
2855
+ function carriesFiles(types) {
2856
+ return types !== void 0 && types.includes("Files");
2857
+ }
2858
+ function droppedFiles(files) {
2859
+ return files === void 0 ? [] : Array.from(files);
2860
+ }
2703
2861
 
2704
2862
  // src/client/icon.tsx
2705
2863
  var import_react2 = require("react");
@@ -2747,7 +2905,7 @@ window.__ModuleLoader__.load({
2747
2905
  }
2748
2906
 
2749
2907
  // src/client/page.tsx
2750
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
2908
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
2751
2909
  var import_react10 = require("react");
2752
2910
 
2753
2911
  // src/client/board.ts
@@ -2757,6 +2915,13 @@ window.__ModuleLoader__.load({
2757
2915
  const pad = (value2) => String(value2).padStart(2, "0");
2758
2916
  return `${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
2759
2917
  }
2918
+ function discussionNodes(sessions) {
2919
+ return sessions.map((session) => ({
2920
+ sessionId: session.sessionId,
2921
+ kind: session.kind,
2922
+ at: session.linkedAt ?? session.preparedAt ?? session.createdAt
2923
+ }));
2924
+ }
2760
2925
  var EMPTY_BOARD = { requirements: [], standalone: [], bugs: [] };
2761
2926
  var STORY_RUNNING = ["developing", "self_testing"];
2762
2927
  var STORY_DONE = ["done", "completed", "cancelled"];
@@ -2887,7 +3052,8 @@ window.__ModuleLoader__.load({
2887
3052
  acceptance: parsed.acceptance,
2888
3053
  bodyMissing: story.bodyMissing,
2889
3054
  ...last === void 0 ? {} : { lastRun: { status: last.status, at: last.at } },
2890
- runs
3055
+ runs,
3056
+ sessions: discussionNodes(story.sessions)
2891
3057
  };
2892
3058
  if (story.requirementId === null) {
2893
3059
  standalone.push({ kind: "story", id: story.id, workspaceId: node.workspaceId, story: node });
@@ -2908,12 +3074,8 @@ window.__ModuleLoader__.load({
2908
3074
  const finished = stories.length > 0 && stories.every((story) => story.status === "completed");
2909
3075
  const state = requirement.status === "archived" ? "done" : running ? "running" : requirement.status === "completed" || finished ? "done" : "ready";
2910
3076
  const branch = runs.find((run) => run.branch.length > 0)?.branch;
2911
- const sessions = snapshot.requirementSessions.filter((session) => session.requirementId === requirement.id).map(
2912
- (session) => ({
2913
- sessionId: session.sessionId,
2914
- kind: session.kind,
2915
- at: session.linkedAt ?? session.preparedAt ?? session.createdAt
2916
- })
3077
+ const sessions = discussionNodes(
3078
+ snapshot.requirementSessions.filter((session) => session.requirementId === requirement.id)
2917
3079
  );
2918
3080
  const latestDesign = sessions.find((session) => session.kind === "design");
2919
3081
  return {
@@ -2952,7 +3114,8 @@ window.__ModuleLoader__.load({
2952
3114
  updatedAt: bug.updatedAt,
2953
3115
  ...last?.sessionId === void 0 ? {} : { sessionId: last.sessionId },
2954
3116
  ...bug.createSessionId === null ? {} : { createSessionId: bug.createSessionId },
2955
- runs
3117
+ runs,
3118
+ sessions: discussionNodes(bug.sessions)
2956
3119
  };
2957
3120
  });
2958
3121
  return { requirements, standalone, bugs };
@@ -3449,6 +3612,8 @@ window.__ModuleLoader__.load({
3449
3612
  const [files, setFiles] = (0, import_react4.useState)([]);
3450
3613
  const [rejections, setRejections] = (0, import_react4.useState)([]);
3451
3614
  const picker = (0, import_react4.useRef)(null);
3615
+ const [dragActive, setDragActive] = (0, import_react4.useState)(false);
3616
+ const dragDepth = (0, import_react4.useRef)(0);
3452
3617
  const previews = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
3453
3618
  const [, bumpPreviews] = (0, import_react4.useState)(0);
3454
3619
  const [broken, setBroken] = (0, import_react4.useState)([]);
@@ -3501,18 +3666,75 @@ window.__ModuleLoader__.load({
3501
3666
  },
3502
3667
  []
3503
3668
  );
3504
- const pick = (event) => {
3505
- const picked = Array.from(event.target.files ?? []);
3506
- event.target.value = "";
3669
+ const addFiles = (picked) => {
3507
3670
  if (picked.length === 0) return;
3508
3671
  const intake = intakeAttachments(picked, files, attachmentPolicy ?? null);
3509
3672
  if (intake.accepted.length > 0) setFiles((current) => [...current, ...intake.accepted]);
3510
3673
  setRejections(intake.rejections);
3511
3674
  };
3675
+ const pick = (event) => {
3676
+ const picked = Array.from(event.target.files ?? []);
3677
+ event.target.value = "";
3678
+ addFiles(picked);
3679
+ };
3680
+ const dropRefused = busy || attachmentAvailable === false;
3681
+ const dragEnter = (event) => {
3682
+ if (!carriesFiles(event.dataTransfer?.types) || dropRefused) return;
3683
+ event.preventDefault();
3684
+ dragDepth.current += 1;
3685
+ setDragActive(true);
3686
+ };
3687
+ const dragOver = (event) => {
3688
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3689
+ event.preventDefault();
3690
+ event.dataTransfer.dropEffect = dropRefused ? "none" : "copy";
3691
+ };
3692
+ const dragLeave = (event) => {
3693
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3694
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
3695
+ if (dragDepth.current === 0) setDragActive(false);
3696
+ };
3697
+ const drop = (event) => {
3698
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3699
+ event.preventDefault();
3700
+ dragDepth.current = 0;
3701
+ setDragActive(false);
3702
+ if (dropRefused) return;
3703
+ addFiles(droppedFiles(event.dataTransfer?.files));
3704
+ };
3705
+ (0, import_react4.useEffect)(() => {
3706
+ if (!dragActive) return;
3707
+ const reset = () => {
3708
+ dragDepth.current = 0;
3709
+ setDragActive(false);
3710
+ };
3711
+ const onKeyDown = (event) => {
3712
+ if (event.key === "Escape") reset();
3713
+ };
3714
+ window.addEventListener("keydown", onKeyDown);
3715
+ window.addEventListener("dragend", reset);
3716
+ window.addEventListener("drop", reset);
3717
+ window.addEventListener("blur", reset);
3718
+ return () => {
3719
+ window.removeEventListener("keydown", onKeyDown);
3720
+ window.removeEventListener("dragend", reset);
3721
+ window.removeEventListener("drop", reset);
3722
+ window.removeEventListener("blur", reset);
3723
+ };
3724
+ }, [dragActive]);
3512
3725
  const remove2 = (index2) => {
3513
3726
  setFiles((current) => current.filter((_, position) => position !== index2));
3514
3727
  setRejections([]);
3515
3728
  };
3729
+ const pageDragOver = (event) => {
3730
+ if (carriesFiles(event.dataTransfer?.types)) event.preventDefault();
3731
+ };
3732
+ const pageDrop = (event) => {
3733
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3734
+ event.preventDefault();
3735
+ dragDepth.current = 0;
3736
+ setDragActive(false);
3737
+ };
3516
3738
  const submit = (event) => {
3517
3739
  event.preventDefault();
3518
3740
  if (busy) return;
@@ -3543,7 +3765,7 @@ window.__ModuleLoader__.load({
3543
3765
  size: formatBytes(attachmentPolicy.maxImageBytes),
3544
3766
  count: attachmentPolicy.maxImagesPerMessage
3545
3767
  });
3546
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create", children: [
3768
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create", onDragOver: pageDragOver, onDrop: pageDrop, children: [
3547
3769
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("header", { className: "tf-detail-head", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-detail-heading", children: [
3548
3770
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "tf-title", children: t("create.title") }),
3549
3771
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "tf-sub", children: t("create.subtitle") })
@@ -3606,88 +3828,100 @@ window.__ModuleLoader__.load({
3606
3828
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-field-hint", children: t("create.descriptionHint") })
3607
3829
  ] })
3608
3830
  ] }),
3609
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "tf-card", children: [
3610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-card-head", children: [
3611
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "tf-card-title", children: t("create.attachment.label") }),
3612
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-card-hint", children: t("create.attachment.optional") })
3613
- ] }),
3614
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-attachment-bar", children: [
3615
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3616
- "button",
3617
- {
3618
- className: "tf-btn",
3619
- "data-tone": "soft",
3620
- type: "button",
3621
- disabled: busy || attachmentAvailable === false,
3622
- onClick: () => picker.current?.click(),
3623
- children: t("create.attachment.add")
3624
- }
3625
- ),
3626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-field-hint tf-grow", children: attachmentHint })
3627
- ] }),
3628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3629
- "input",
3630
- {
3631
- ref: picker,
3632
- className: "tf-attachment-input",
3633
- type: "file",
3634
- multiple: true,
3635
- tabIndex: -1,
3636
- "aria-hidden": true,
3637
- onChange: pick
3638
- }
3639
- ),
3640
- files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-list", children: files.map((file, index2) => {
3641
- const state = uploads[fileKey(file)];
3642
- const label = state === void 0 ? null : state.status === "uploaded" ? t("create.attachment.uploaded") : state.status === "failed" ? t("create.attachment.failed") : state.status === "uploading" ? state.percent === void 0 ? t("create.attachment.uploading") : t("create.attachment.uploadingPercent", { percent: state.percent }) : t("create.attachment.queued");
3643
- const key = fileKey(file);
3644
- const preview = broken.includes(key) ? void 0 : previews.current.get(key);
3645
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "tf-attachment-item", "data-state": state?.status, children: [
3646
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-thumb", "data-kind": isImageFile(file) ? "image" : "file", children: preview === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-ext", children: fileExtension(file.name) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3647
- "img",
3648
- {
3649
- className: "tf-attachment-thumb-img",
3650
- src: preview,
3651
- alt: file.name,
3652
- onError: () => setBroken((current) => current.includes(key) ? current : [...current, key])
3653
- }
3654
- ) }),
3655
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-body", children: [
3656
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-name", title: file.name, children: file.name }),
3657
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-meta", children: [
3658
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-size", children: formatBytes(file.size) }),
3659
- label === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3660
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-sep", "aria-hidden": true, children: t("create.attachment.metaSep") }),
3661
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-status", children: label })
3662
- ] })
3663
- ] }),
3664
- state?.status === "failed" && state.reason !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-error", children: t(FAILURE_KEY[state.reason]) }) : null,
3665
- state?.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-track", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3666
- "span",
3667
- {
3668
- className: "tf-attachment-fill",
3669
- style: { width: `${Math.max(4, Math.min(100, state.percent ?? 0))}%` }
3670
- }
3671
- ) }) : null
3831
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3832
+ "section",
3833
+ {
3834
+ className: "tf-card tf-attachment-dropzone",
3835
+ "data-drag": dragActive ? "active" : void 0,
3836
+ onDragEnter: dragEnter,
3837
+ onDragOver: dragOver,
3838
+ onDragLeave: dragLeave,
3839
+ onDrop: drop,
3840
+ children: [
3841
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-card-head", children: [
3842
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "tf-card-title", children: t("create.attachment.label") }),
3843
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-card-hint", children: t("create.attachment.optional") })
3672
3844
  ] }),
3673
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3674
3846
  "button",
3675
3847
  {
3676
- className: "tf-attachment-remove",
3848
+ className: "tf-attachment-pick",
3677
3849
  type: "button",
3678
- "aria-label": `${t("create.attachment.remove")} ${file.name}`,
3679
- disabled: busy,
3680
- onClick: () => remove2(index2),
3681
- children: "×"
3850
+ disabled: busy || attachmentAvailable === false,
3851
+ onClick: () => picker.current?.click(),
3852
+ children: [
3853
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-plus", "aria-hidden": "true", children: "+" }),
3854
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-pick-title", children: t("create.attachment.add") }),
3855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-pick-sub", children: dragActive ? t("create.attachment.dropActive") : t("create.attachment.dropHint") })
3856
+ ]
3682
3857
  }
3683
- )
3684
- ] }, `${fileKey(file)}-${index2}`);
3685
- }) }) : null,
3686
- rejections.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-rejected", role: "alert", children: rejections.map((rejection, index2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: t(REJECTION_KEY[rejection.reason], {
3687
- name: rejection.file.name,
3688
- ...rejection.params
3689
- }) }, `${rejection.file.name}-${index2}`)) }) : null
3690
- ] }),
3858
+ ),
3859
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "tf-attachment-limits", children: attachmentHint }),
3860
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3861
+ "input",
3862
+ {
3863
+ ref: picker,
3864
+ className: "tf-attachment-input",
3865
+ type: "file",
3866
+ multiple: true,
3867
+ tabIndex: -1,
3868
+ "aria-hidden": true,
3869
+ onChange: pick
3870
+ }
3871
+ ),
3872
+ files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-list", children: files.map((file, index2) => {
3873
+ const state = uploads[fileKey(file)];
3874
+ const label = state === void 0 ? null : state.status === "uploaded" ? t("create.attachment.uploaded") : state.status === "failed" ? t("create.attachment.failed") : state.status === "uploading" ? state.percent === void 0 ? t("create.attachment.uploading") : t("create.attachment.uploadingPercent", { percent: state.percent }) : t("create.attachment.queued");
3875
+ const key = fileKey(file);
3876
+ const preview = broken.includes(key) ? void 0 : previews.current.get(key);
3877
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "tf-attachment-item", "data-state": state?.status, children: [
3878
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-thumb", "data-kind": isImageFile(file) ? "image" : "file", children: preview === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-ext", children: fileExtension(file.name) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3879
+ "img",
3880
+ {
3881
+ className: "tf-attachment-thumb-img",
3882
+ src: preview,
3883
+ alt: file.name,
3884
+ onError: () => setBroken((current) => current.includes(key) ? current : [...current, key])
3885
+ }
3886
+ ) }),
3887
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-body", children: [
3888
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-name", title: file.name, children: file.name }),
3889
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-meta", children: [
3890
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-size", children: formatBytes(file.size) }),
3891
+ label === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3892
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-sep", "aria-hidden": true, children: t("create.attachment.metaSep") }),
3893
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-status", children: label })
3894
+ ] })
3895
+ ] }),
3896
+ state?.status === "failed" && state.reason !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-error", children: t(FAILURE_KEY[state.reason]) }) : null,
3897
+ state?.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-track", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3898
+ "span",
3899
+ {
3900
+ className: "tf-attachment-fill",
3901
+ style: { width: `${Math.max(4, Math.min(100, state.percent ?? 0))}%` }
3902
+ }
3903
+ ) }) : null
3904
+ ] }),
3905
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3906
+ "button",
3907
+ {
3908
+ className: "tf-attachment-remove",
3909
+ type: "button",
3910
+ "aria-label": `${t("create.attachment.remove")} ${file.name}`,
3911
+ disabled: busy,
3912
+ onClick: () => remove2(index2),
3913
+ children: "×"
3914
+ }
3915
+ )
3916
+ ] }, `${fileKey(file)}-${index2}`);
3917
+ }) }) : null,
3918
+ rejections.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-rejected", role: "alert", children: rejections.map((rejection, index2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: t(REJECTION_KEY[rejection.reason], {
3919
+ name: rejection.file.name,
3920
+ ...rejection.params
3921
+ }) }, `${rejection.file.name}-${index2}`)) }) : null
3922
+ ]
3923
+ }
3924
+ ),
3691
3925
  invalid !== null || error !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "tf-error", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AlertText, { text: invalid?.message ?? error ?? "" }) }) : null,
3692
3926
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create-actions", children: [
3693
3927
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "tf-btn", "data-tone": "primary", type: "submit", disabled: !canSubmit, children: staging ? t("create.attachment.submitUploading") : busy ? t("create.submitting") : t("create.submit") }),
@@ -13852,7 +14086,7 @@ window.__ModuleLoader__.load({
13852
14086
  },
13853
14087
  [basePath, onMissing, onOpenPath]
13854
14088
  );
13855
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives.MarkdownDelegateProvider, { openFile: basePath === void 0 ? void 0 : openFile, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives.MarkdownText, { text, labels }) });
14089
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives2.MarkdownDelegateProvider, { openFile: basePath === void 0 ? void 0 : openFile, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives2.MarkdownText, { text, labels }) });
13856
14090
  }
13857
14091
  function SectionHead({
13858
14092
  title,
@@ -13908,6 +14142,7 @@ window.__ModuleLoader__.load({
13908
14142
  const [createBusy, setCreateBusy] = (0, import_react10.useState)(false);
13909
14143
  const [createError, setCreateError] = (0, import_react10.useState)(null);
13910
14144
  const [discussionBusy, setDiscussionBusy] = (0, import_react10.useState)(null);
14145
+ const [recordDiscussionBusy, setRecordDiscussionBusy] = (0, import_react10.useState)(null);
13911
14146
  const [cleanup, setCleanup] = (0, import_react10.useState)(false);
13912
14147
  const [selected2, setSelected] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
13913
14148
  const [confirm, setConfirm] = (0, import_react10.useState)(null);
@@ -14411,14 +14646,59 @@ window.__ModuleLoader__.load({
14411
14646
  },
14412
14647
  [data, openConversation2, showToast, t]
14413
14648
  );
14649
+ const startRecordDiscussion = (0, import_react10.useCallback)(
14650
+ (subject) => {
14651
+ const open = openConversation2;
14652
+ if (open === void 0) {
14653
+ showToast(t("toast.startUnavailable"));
14654
+ return;
14655
+ }
14656
+ setRecordDiscussionBusy(subject.kind);
14657
+ open(subject.workspaceId, buildRecordDiscussionPrompt(subject.kind, subject.title), {
14658
+ agentPreset: presetForRecordDiscussion(subject.kind),
14659
+ autoSend: false,
14660
+ composerKind: subject.kind
14661
+ }).then(async (outcome) => {
14662
+ if (outcome.status === "unavailable") {
14663
+ if (mounted.current) {
14664
+ showToast(outcome.message ?? t("toast.discussionFailed", { title: subject.title }));
14665
+ }
14666
+ return;
14667
+ }
14668
+ if (outcome.sessionId === void 0) {
14669
+ if (mounted.current) {
14670
+ showToast(t("toast.discussionDrafted", { title: subject.title }));
14671
+ }
14672
+ return;
14673
+ }
14674
+ try {
14675
+ await data.prepareDiscussionSession(subject.kind, subject.id, outcome.sessionId, "discuss");
14676
+ if (!mounted.current) return;
14677
+ showToast(
14678
+ outcome.status === "drafted" ? t("toast.discussionDrafted", { title: subject.title }) : t("toast.discussionStarted", { title: subject.title })
14679
+ );
14680
+ } catch (failure) {
14681
+ if (!mounted.current) return;
14682
+ showToast(
14683
+ t("toast.discussionLinkFailed", {
14684
+ title: subject.title,
14685
+ message: failureMessage(failure)
14686
+ })
14687
+ );
14688
+ }
14689
+ }).catch(() => {
14690
+ if (mounted.current) showToast(t("toast.discussionFailed", { title: subject.title }));
14691
+ }).finally(() => {
14692
+ if (mounted.current) setRecordDiscussionBusy(null);
14693
+ });
14694
+ },
14695
+ [data, openConversation2, showToast, t]
14696
+ );
14414
14697
  const requirementAction = (0, import_react10.useCallback)(
14415
14698
  (requirement) => {
14416
14699
  if (requirement.state === "running") {
14417
14700
  return { label: t("row.open"), tone: "live", icon: ICONS.live };
14418
14701
  }
14419
- if (requirement.state === "done") {
14420
- return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
14421
- }
14422
14702
  const lone = loneReadyStory(requirement);
14423
14703
  return lone === void 0 ? null : { label: t("row.start"), tone: "soft", icon: ICONS.soft };
14424
14704
  },
@@ -14513,11 +14793,7 @@ window.__ModuleLoader__.load({
14513
14793
  }
14514
14794
  };
14515
14795
  const storyAction = (0, import_react10.useCallback)(
14516
- (story) => {
14517
- if (story.state === "running") return { label: t("row.open"), tone: "live", icon: ICONS.live };
14518
- if (story.state === "done") return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
14519
- return { label: t("row.start"), tone: "soft", icon: ICONS.soft };
14520
- },
14796
+ (story) => story.state === "ready" ? { label: t("row.start"), tone: "soft", icon: ICONS.soft } : null,
14521
14797
  [t]
14522
14798
  );
14523
14799
  const runStoryAction = (0, import_react10.useCallback)(
@@ -15043,7 +15319,14 @@ window.__ModuleLoader__.load({
15043
15319
  onDelete: () => openDelete([{ kind: "bug", id: activeBug.id }]),
15044
15320
  onComplete: () => setDecision({ kind: "complete-bug", bug: activeBug, busy: false, error: null }),
15045
15321
  onStartFix: (tier) => startBugFix(activeBug, tier),
15046
- onCancelFix: () => cancelBugFix(activeBug)
15322
+ onCancelFix: () => cancelBugFix(activeBug),
15323
+ onNewDiscussion: () => startRecordDiscussion({
15324
+ kind: "bug",
15325
+ id: activeBug.id,
15326
+ title: activeBug.title,
15327
+ workspaceId: activeBug.workspaceId
15328
+ }),
15329
+ discussionBusy: recordDiscussionBusy === "bug"
15047
15330
  }
15048
15331
  ) : null,
15049
15332
  view.kind === "story" && activeStory !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -15063,7 +15346,14 @@ window.__ModuleLoader__.load({
15063
15346
  onCancelRun: () => setDecision({ kind: "cancel-run", story: activeStory, busy: false, error: null }),
15064
15347
  onOpenSession: (sessionId) => openSession(sessionId),
15065
15348
  onOpenPath: openFile,
15066
- onMissing: (path) => showToast(t("doc.linkMissing", { path }))
15349
+ onMissing: (path) => showToast(t("doc.linkMissing", { path })),
15350
+ onNewDiscussion: () => startRecordDiscussion({
15351
+ kind: "story",
15352
+ id: activeStory.id,
15353
+ title: activeStory.title,
15354
+ workspaceId: activeStory.workspaceId
15355
+ }),
15356
+ discussionBusy: recordDiscussionBusy === "story"
15067
15357
  }
15068
15358
  ) : null
15069
15359
  ] }) }),
@@ -15940,6 +16230,8 @@ window.__ModuleLoader__.load({
15940
16230
  onComplete,
15941
16231
  onStartFix,
15942
16232
  onCancelFix,
16233
+ onNewDiscussion,
16234
+ discussionBusy = false,
15943
16235
  readOnly = false
15944
16236
  }) {
15945
16237
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
@@ -15966,27 +16258,42 @@ window.__ModuleLoader__.load({
15966
16258
  readOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: "tf-btn tf-danger", "data-tone": "ghost", type: "button", onClick: onDelete, style: { flex: "0 0 auto" }, children: [
15967
16259
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TrashGlyph, {}),
15968
16260
  t("delete.action")
15969
- ] }),
15970
- readOnly || bug.sessionId === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
15971
- "button",
15972
- {
15973
- className: "tf-btn",
15974
- "data-tone": "live",
15975
- type: "button",
15976
- onClick: () => onOpenSession(bug.sessionId),
15977
- style: { flex: "0 0 auto" },
15978
- children: [
15979
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatGlyph, {}),
15980
- t("row.open")
15981
- ]
15982
- }
15983
- )
16261
+ ] })
15984
16262
  ] }),
15985
16263
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
15986
16264
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
15987
16265
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
15988
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHead, { title: t("detail.bugBody") }),
15989
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: bug.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", children: t("body.missingGeneric") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkdownBody, { text: bug.content.join("\n"), t }) })
16266
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16267
+ SectionHead,
16268
+ {
16269
+ title: t("detail.bugBody"),
16270
+ action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
16271
+ "button",
16272
+ {
16273
+ className: "tf-btn tf-btn-sm",
16274
+ "data-tone": "soft",
16275
+ type: "button",
16276
+ disabled: discussionBusy,
16277
+ onClick: onNewDiscussion,
16278
+ children: [
16279
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
16280
+ discussionBusy ? t("detail.discussionStarting") : t("detail.bugDiscussion")
16281
+ ]
16282
+ }
16283
+ )
16284
+ }
16285
+ ),
16286
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: bug.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", children: t("body.missingGeneric") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkdownBody, { text: bug.content.join("\n"), t }) }),
16287
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16288
+ DiscussionSessions,
16289
+ {
16290
+ t,
16291
+ sessions: bug.sessions,
16292
+ sessionTitle,
16293
+ onOpenSession,
16294
+ readOnly
16295
+ }
16296
+ )
15990
16297
  ] }),
15991
16298
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
15992
16299
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -16062,6 +16369,8 @@ window.__ModuleLoader__.load({
16062
16369
  onOpenSession,
16063
16370
  onOpenPath,
16064
16371
  onMissing,
16372
+ onNewDiscussion,
16373
+ discussionBusy = false,
16065
16374
  readOnly = false
16066
16375
  }) {
16067
16376
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
@@ -16111,7 +16420,26 @@ window.__ModuleLoader__.load({
16111
16420
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
16112
16421
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
16113
16422
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16114
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHead, { title: t("detail.storyBody") }),
16423
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16424
+ SectionHead,
16425
+ {
16426
+ title: t("detail.storyBody"),
16427
+ action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
16428
+ "button",
16429
+ {
16430
+ className: "tf-btn tf-btn-sm",
16431
+ "data-tone": "soft",
16432
+ type: "button",
16433
+ disabled: discussionBusy,
16434
+ onClick: onNewDiscussion,
16435
+ children: [
16436
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
16437
+ discussionBusy ? t("detail.discussionStarting") : t("detail.storyDiscussion")
16438
+ ]
16439
+ }
16440
+ )
16441
+ }
16442
+ ),
16115
16443
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: story.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("body.missing", { path: story.path }) }) : story.body.trim() === "" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("detail.noDescription") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16116
16444
  MarkdownBody,
16117
16445
  {
@@ -16121,7 +16449,17 @@ window.__ModuleLoader__.load({
16121
16449
  onOpenPath,
16122
16450
  onMissing
16123
16451
  }
16124
- ) })
16452
+ ) }),
16453
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16454
+ DiscussionSessions,
16455
+ {
16456
+ t,
16457
+ sessions: story.sessions,
16458
+ sessionTitle,
16459
+ onOpenSession,
16460
+ readOnly
16461
+ }
16462
+ )
16125
16463
  ] }),
16126
16464
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16127
16465
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -16891,6 +17229,9 @@ window.__ModuleLoader__.load({
16891
17229
  prepareRequirementSession: async (requirementId, sessionId, kind) => callRemote(
16892
17230
  (await ready.promise).prepareRequirementSession(requirementId, sessionId, kind, locale())
16893
17231
  ),
17232
+ prepareDiscussionSession: async (subjectType, subjectId, sessionId, kind) => callRemote(
17233
+ (await ready.promise).prepareDiscussionSession(subjectType, subjectId, sessionId, kind, locale())
17234
+ ),
16894
17235
  sessionPermissionTier: async (sessionId) => callRemote((await ready.promise).sessionPermissionTier(sessionId, locale())),
16895
17236
  previewDelete: async (targets) => callRemote((await ready.promise).previewDelete(targets, locale())),
16896
17237
  deleteRecords: async (targets) => callRemote((await ready.promise).deleteRecords(targets, locale())),