@emberai-engg/task-board 0.3.6 → 0.4.1

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/dist/index.d.mts CHANGED
@@ -6,12 +6,29 @@ interface Project {
6
6
  slug: string;
7
7
  name: string;
8
8
  }
9
+ /**
10
+ * The four description sections that support inline editing, Draft/Approved
11
+ * status, and highlight-to-comment anchors.
12
+ *
13
+ * `open_questions` is kept on `StructuredDescription` for backwards
14
+ * compatibility but is no longer surfaced in the description UI — the
15
+ * Outstanding Questions section uses a structured collection instead.
16
+ */
17
+ type SectionKey = "problem" | "user_story" | "proposed_behavior" | "acceptance_criteria";
18
+ type SectionStatus = "draft" | "approved";
9
19
  interface StructuredDescription {
10
20
  problem: string;
11
21
  user_story: string;
12
22
  proposed_behavior: string;
13
23
  acceptance_criteria: string;
24
+ /**
25
+ * Legacy free-text questions field. Kept for backwards compatibility with
26
+ * tasks created before the structured Outstanding Questions feature.
27
+ * Not rendered in the current detail UI.
28
+ */
14
29
  open_questions: string;
30
+ /** Per-section review status, e.g. `{ problem: "approved" }`. Defaults to draft when absent. */
31
+ section_status?: Record<string, SectionStatus>;
15
32
  }
16
33
  interface Task {
17
34
  id: string;
@@ -38,6 +55,11 @@ interface ActivityEntry {
38
55
  user_name: string;
39
56
  created_at: string;
40
57
  }
58
+ type ThreadStatus = "active" | "complete";
59
+ interface ThreadAnchor {
60
+ section: SectionKey;
61
+ snippet: string;
62
+ }
41
63
  interface Comment {
42
64
  id: string;
43
65
  task_id: string;
@@ -48,6 +70,50 @@ interface Comment {
48
70
  edited?: boolean;
49
71
  edited_at?: string;
50
72
  created_at: string;
73
+ /** Set on replies; null/undefined for top-level (thread root) comments. */
74
+ parent_id?: string | null;
75
+ /** Top-level (thread) metadata. Ignored on replies. */
76
+ title?: string | null;
77
+ thread_status?: ThreadStatus;
78
+ anchor?: ThreadAnchor | null;
79
+ attachment_ids?: string[];
80
+ }
81
+ type QuestionStatus = "awaiting" | "answered";
82
+ interface QuestionReply {
83
+ id: string;
84
+ question_id: string;
85
+ task_id: string;
86
+ content: string;
87
+ author_id: string;
88
+ author_name: string;
89
+ created_at: string;
90
+ }
91
+ interface Question {
92
+ id: string;
93
+ task_id: string;
94
+ text: string;
95
+ status: QuestionStatus;
96
+ asked_by: string;
97
+ asked_by_name: string;
98
+ answered_by_reply_id?: string | null;
99
+ replies: QuestionReply[];
100
+ created_at: string;
101
+ updated_at: string;
102
+ }
103
+ type AttachmentKind = "image" | "file" | "link";
104
+ interface Attachment {
105
+ id: string;
106
+ task_id: string;
107
+ kind: AttachmentKind;
108
+ name: string;
109
+ size?: number | null;
110
+ content_type?: string | null;
111
+ /** Read URL — signed GCS URL for image/file uploads, raw URL for links. */
112
+ url: string;
113
+ storage_path?: string | null;
114
+ uploaded_by: string;
115
+ uploaded_by_name: string;
116
+ created_at: string;
51
117
  }
52
118
  interface Notification {
53
119
  id: string;
@@ -73,13 +139,19 @@ type ColumnUnreads = Record<string, number>;
73
139
  interface ColumnConfig {
74
140
  key: string;
75
141
  label: string;
142
+ /** Tailwind class for the round status dot, e.g. `bg-amber-500`. */
76
143
  color: string;
144
+ /** Optional Tailwind classes for the inline status pill (background + text). */
145
+ chip?: string;
77
146
  description: string;
78
147
  }
79
148
  interface PriorityConfig {
80
149
  value: string;
81
150
  label: string;
151
+ /** Tailwind classes for the priority pill (bg + text + border). */
82
152
  className: string;
153
+ /** Optional Tailwind class for a small priority dot, e.g. `bg-amber-500`. */
154
+ dot?: string;
83
155
  }
84
156
  interface TagConfig {
85
157
  value: string;
@@ -87,8 +159,10 @@ interface TagConfig {
87
159
  className: string;
88
160
  }
89
161
  interface DescriptionSectionConfig {
90
- key: keyof StructuredDescription;
162
+ key: SectionKey;
91
163
  label: string;
164
+ /** Placeholder text shown in the editor when the section is empty. */
165
+ placeholder?: string;
92
166
  }
93
167
  interface TaskBoardUser {
94
168
  username: string;
@@ -130,6 +204,10 @@ interface ColumnResponse {
130
204
  interface TaskDetailResponse extends Task {
131
205
  comments: Comment[];
132
206
  activity: ActivityEntry[];
207
+ /** Embedded structured questions (with their replies). */
208
+ questions?: Question[];
209
+ /** Embedded attachments with read URLs already computed server-side. */
210
+ attachments?: Attachment[];
133
211
  }
134
212
  interface NotificationCountResponse {
135
213
  count: number;
@@ -153,10 +231,36 @@ interface UpdateTaskPayload {
153
231
  interface CreateCommentPayload {
154
232
  content: string;
155
233
  is_internal?: boolean;
234
+ /** Set on replies. Top-level (thread) comments leave this null/undefined. */
235
+ parent_id?: string | null;
236
+ /** Optional title for top-level threads. */
237
+ title?: string | null;
238
+ /** Anchor metadata for highlight-to-comment threads. */
239
+ anchor?: ThreadAnchor | null;
240
+ /** Attachment IDs referenced from this comment. */
241
+ attachment_ids?: string[];
156
242
  }
157
243
  interface EditCommentPayload {
158
244
  content: string;
159
245
  }
246
+ interface UpdateThreadPayload {
247
+ title?: string;
248
+ thread_status?: ThreadStatus;
249
+ }
250
+ interface CreateQuestionPayload {
251
+ text: string;
252
+ }
253
+ interface UpdateQuestionPayload {
254
+ text?: string;
255
+ status?: QuestionStatus;
256
+ }
257
+ interface CreateQuestionReplyPayload {
258
+ content: string;
259
+ }
260
+ interface CreateLinkAttachmentPayload {
261
+ url: string;
262
+ name?: string;
263
+ }
160
264
 
161
265
  interface TaskBoardProps {
162
266
  /** Optional class name for the outer container */
@@ -284,6 +388,355 @@ interface TaskDetailPanelProps {
284
388
  }
285
389
  declare function TaskDetailPanel({ task, projectSlug, onClose, onUpdate }: TaskDetailPanelProps): react_jsx_runtime.JSX.Element;
286
390
 
391
+ interface TaskDetailViewProps {
392
+ taskId: string;
393
+ /** href for the "Back to Task Board" link. Ignored if `onBack` is provided. */
394
+ backHref?: string;
395
+ /** Called when the back link is clicked. Use this for SPA routing. */
396
+ onBack?: () => void;
397
+ /** Optional slot above the main content (e.g. consumer's breadcrumb bar). */
398
+ breadcrumb?: React__default.ReactNode;
399
+ /** Called after a successful delete; consumer should navigate away. */
400
+ onDeleted?: (task: Task) => void;
401
+ /**
402
+ * Provide this to enable the prev/next buttons. The view fetches the
403
+ * project's task list and computes neighbors; on click, the consumer
404
+ * is responsible for navigating to /task-board/[id]?project=<slug>.
405
+ */
406
+ onNavigateToTask?: (taskId: string, projectSlug: string) => void;
407
+ /** Build a share URL for the task. Defaults to `${origin}/task-board/${id}?project=${slug}`. */
408
+ buildShareUrl?: (task: Task) => string;
409
+ }
410
+ /**
411
+ * Full-page task detail orchestrator. Renders the breadcrumb slot, back +
412
+ * prev/next chrome, the task ID/project header, inline-editable title,
413
+ * status/priority/menu action cluster, all description sections, outstanding
414
+ * questions, attachments, and the right-side threads panel with
415
+ * highlight-to-comment.
416
+ *
417
+ * Consumers are responsible for app chrome (sidebar, breadcrumb bar) — pass
418
+ * the breadcrumb via `breadcrumb` and place this inside your existing layout.
419
+ */
420
+ declare function TaskDetailView({ taskId, backHref, onBack, breadcrumb, onDeleted, onNavigateToTask, buildShareUrl, }: TaskDetailViewProps): react_jsx_runtime.JSX.Element;
421
+
422
+ interface DescriptionSectionProps {
423
+ sectionKey: SectionKey;
424
+ label: string;
425
+ placeholder: string;
426
+ /** Markdown content. */
427
+ value: string;
428
+ /** Called with the new markdown when the user commits an edit. */
429
+ onChange: (next: string) => void;
430
+ /** Current Draft/Approved status; defaults to "draft" when omitted. */
431
+ status: SectionStatus;
432
+ /** Toggle Draft ↔ Approved. */
433
+ onStatusChange: (next: SectionStatus) => void;
434
+ /** Optional saving indicator passed through to the editor. */
435
+ saving?: boolean;
436
+ }
437
+ /**
438
+ * One free-text description section (Problem / User Story / etc.) with:
439
+ * - segmented Draft/Approved toggle and hover-card explanations
440
+ * - read-mode `MarkdownView`
441
+ * - click-to-focus WYSIWYG `MarkdownEditor`
442
+ * - `data-section={sectionKey}` so highlight-to-comment can anchor here
443
+ */
444
+ declare function DescriptionSection({ sectionKey, label, placeholder, value, onChange, status, onStatusChange, saving, }: DescriptionSectionProps): react_jsx_runtime.JSX.Element;
445
+
446
+ /**
447
+ * Lightweight read-mode markdown renderer for description sections, thread
448
+ * messages, and other static markdown content. Supports H1/H2/H3, bullet lists,
449
+ * numbered lists, blockquotes, task lists (`- [ ] foo`), inline bold (`**`),
450
+ * inline code, and `@[Name](username)` mentions rendered as styled pills.
451
+ *
452
+ * For editing, use `MarkdownEditor` (WYSIWYG, contenteditable).
453
+ */
454
+ declare function MarkdownView({ content, className }: {
455
+ content: string;
456
+ className?: string;
457
+ }): react_jsx_runtime.JSX.Element | null;
458
+
459
+ interface MarkdownEditorProps {
460
+ /** Markdown content. Used once on mount; the contenteditable owns state from then on. */
461
+ value: string;
462
+ /** Called with the canonical markdown when the user blurs the editor. */
463
+ onCommit: (md: string) => void;
464
+ /** Called when the user presses Escape. */
465
+ onCancel: () => void;
466
+ /** Placeholder shown when the editor is empty. */
467
+ placeholder: string;
468
+ /** Optional saving indicator. */
469
+ saving?: boolean;
470
+ }
471
+ /**
472
+ * WYSIWYG markdown editor. Uses a contenteditable div with execCommand
473
+ * formatting so users never see raw `**` / `- ` / `# ` syntax. Storage
474
+ * format remains markdown via `mdToHtml`/`htmlToMd` round-trip helpers,
475
+ * so existing readers (`MarkdownView`, server-side previews) keep working.
476
+ *
477
+ * Pair with the CSS in `task-board.css` (`.eb-tb-markdown-editor`).
478
+ */
479
+ declare function MarkdownEditor({ value, onCommit, onCancel, placeholder, saving }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
480
+
481
+ interface OutstandingQuestionsSectionProps {
482
+ /** All questions for the task. */
483
+ questions: Question[];
484
+ /** Username of the logged-in user, for permission checks (delete-own). */
485
+ currentUsername: string;
486
+ /** Create a new question. */
487
+ onCreate: (text: string) => Promise<void> | void;
488
+ /** Toggle question status (awaiting ↔ answered). */
489
+ onSetStatus: (questionId: string, status: 'awaiting' | 'answered') => Promise<void> | void;
490
+ /** Delete a question (and its replies). */
491
+ onDelete: (questionId: string) => Promise<void> | void;
492
+ /** Add a reply to a question. */
493
+ onAddReply: (questionId: string, content: string) => Promise<void> | void;
494
+ }
495
+ /**
496
+ * Structured list of outstanding questions with awaiting/answered filter,
497
+ * inline composer, and per-question collapsible threads. Replaces the legacy
498
+ * free-text `open_questions` description field.
499
+ */
500
+ declare function OutstandingQuestionsSection({ questions, currentUsername, onCreate, onSetStatus, onDelete, onAddReply, }: OutstandingQuestionsSectionProps): react_jsx_runtime.JSX.Element;
501
+
502
+ interface AttachmentsSectionProps {
503
+ attachments: Attachment[];
504
+ /** Upload a file (image or document). */
505
+ onUpload: (file: File) => Promise<unknown> | void;
506
+ /** Add an external link/recording. */
507
+ onAddLink: (url: string, name?: string) => Promise<unknown> | void;
508
+ /** Delete an attachment by id. */
509
+ onDelete: (attachmentId: string) => Promise<unknown> | void;
510
+ }
511
+ /**
512
+ * Three-subgroup attachments display (Images / Files / Links & recordings)
513
+ * with an "Add attachment" popover and an "Add link or recording" modal.
514
+ *
515
+ * Bug #3 fix: Cancel/Save buttons in the link modal use `inline-flex
516
+ * items-center justify-center` (without these, the button text wasn't
517
+ * vertically centered) and `text-[13px]` (was `text-[12px]`).
518
+ */
519
+ declare function AttachmentsSection({ attachments, onUpload, onAddLink, onDelete, }: AttachmentsSectionProps): react_jsx_runtime.JSX.Element;
520
+
521
+ /**
522
+ * UI shape derived from Comment + Attachment data. A `Thread` is a top-level
523
+ * comment (no `parent_id`) augmented with metadata, replies, and resolved
524
+ * attachment objects (looked up by id).
525
+ */
526
+ interface Thread {
527
+ id: string;
528
+ title: string;
529
+ /** True when title was derived from content rather than persisted on the row. */
530
+ titleDerived: boolean;
531
+ preview: string;
532
+ author_id: string;
533
+ author_name: string;
534
+ created_at: string;
535
+ is_internal: boolean;
536
+ status: ThreadStatus;
537
+ /** Original mention-tagged text, used when rendering the message. */
538
+ rawContent: string;
539
+ anchor?: ThreadAnchor | null;
540
+ attachments: Attachment[];
541
+ replies: ThreadReply[];
542
+ }
543
+ interface ThreadReply {
544
+ id: string;
545
+ author_id: string;
546
+ author_name: string;
547
+ content: string;
548
+ created_at: string;
549
+ is_internal: boolean;
550
+ }
551
+ /** Group a flat comments list into threads (top-level + replies + resolved attachments). */
552
+ declare function deriveThreads(comments: Comment[], attachments: Attachment[]): Thread[];
553
+ /** Display label for a description section key, falling back to the key itself. */
554
+ declare function sectionLabel(key: SectionKey): string;
555
+ /** Compact relative time like "5m ago", "3h ago", "2d ago", or a short date. */
556
+ declare function timeAgo(dateStr: string): string;
557
+
558
+ interface ThreadsPanelProps {
559
+ threads: Thread[];
560
+ activity: ActivityEntry[];
561
+ attachments: Attachment[];
562
+ /** Open / close state. Lift this to the page so the bubble + main padding can coordinate. */
563
+ open: boolean;
564
+ onToggle: () => void;
565
+ /** Currently-open thread (null = list view). Lifted so panel + page agree. */
566
+ openThreadId: string | null;
567
+ onOpenThread: (id: string | null) => void;
568
+ /** Highlight-to-comment anchor pending placement on a new thread. */
569
+ pendingAnchor: ThreadAnchor | null;
570
+ onClearAnchor: () => void;
571
+ /** Click handler for anchor pills — should scroll/pulse the anchored section. */
572
+ onAnchorClick: (anchor: ThreadAnchor) => void;
573
+ /** Set of thread ids currently shimmering (no user-supplied title yet). */
574
+ shimmeringThreadIds: Set<string>;
575
+ /** Whether the current user is allowed to post internal-only threads/replies. */
576
+ isInternalUser: boolean;
577
+ /** Submit a brand-new top-level thread. */
578
+ onCreateThread: (data: {
579
+ title: string;
580
+ content: string;
581
+ isInternal: boolean;
582
+ anchor: ThreadAnchor | null;
583
+ attachmentIds: string[];
584
+ }) => Promise<void>;
585
+ /** Submit a reply on an existing thread. */
586
+ onCreateReply: (parentId: string, content: string, isInternal: boolean) => Promise<void>;
587
+ /** Update title or status on an existing thread. */
588
+ onUpdateThread: (threadId: string, body: {
589
+ title?: string;
590
+ thread_status?: ThreadStatus;
591
+ }) => Promise<void>;
592
+ /** Upload a file attachment for the current task. Returns the new row. */
593
+ onUploadAttachment: (file: File) => Promise<Attachment>;
594
+ /** Add a link/recording attachment for the current task. */
595
+ onAddLinkAttachment: (url: string, name?: string) => Promise<Attachment>;
596
+ }
597
+ /**
598
+ * Right-side panel with Threads / Activity tabs, collapse toggle, thread list,
599
+ * thread detail view, and the new-thread composer. Designed to be `xl:flex
600
+ * hidden` and `fixed` to the right edge by the consuming page.
601
+ */
602
+ declare function ThreadsPanel({ threads, activity, attachments, open, onToggle, openThreadId, onOpenThread, pendingAnchor, onClearAnchor, onAnchorClick, shimmeringThreadIds, isInternalUser, onCreateThread, onCreateReply, onUpdateThread, onUploadAttachment, onAddLinkAttachment, }: ThreadsPanelProps): react_jsx_runtime.JSX.Element;
603
+
604
+ interface ThreadCardProps {
605
+ thread: Thread;
606
+ /** Open the full thread detail view. */
607
+ onOpen: () => void;
608
+ /** Click handler for an anchor pill — should scroll/pulse the anchored section. */
609
+ onAnchorClick: (anchor: ThreadAnchor) => void;
610
+ /** When true, render the title as a shimmer placeholder ("AI is generating a title"). */
611
+ shimmer?: boolean;
612
+ }
613
+ /**
614
+ * One row in the threads list. Click anywhere on the card to open the
615
+ * thread detail view. Anchor pills stop propagation so they can scroll
616
+ * back to the highlighted description section instead.
617
+ */
618
+ declare function ThreadCard({ thread, onOpen, onAnchorClick, shimmer }: ThreadCardProps): react_jsx_runtime.JSX.Element;
619
+
620
+ interface ThreadDetailViewProps {
621
+ thread: Thread;
622
+ onBack: () => void;
623
+ onReply: (content: string, isInternal: boolean) => Promise<void>;
624
+ onUpdateThread: (body: {
625
+ title?: string;
626
+ thread_status?: ThreadStatus;
627
+ }) => Promise<void>;
628
+ onAnchorClick: (anchor: ThreadAnchor) => void;
629
+ /** Whether the current user can post internal-only replies. */
630
+ isInternalUser: boolean;
631
+ }
632
+ /** Detail view shown inside the threads panel when a thread card is clicked. */
633
+ declare function ThreadDetailView({ thread, onBack, onReply, onUpdateThread, onAnchorClick, isInternalUser, }: ThreadDetailViewProps): react_jsx_runtime.JSX.Element;
634
+
635
+ interface ThreadComposerProps {
636
+ /** Existing attachments on the task — used to render chips for already-uploaded files. */
637
+ attachments: Attachment[];
638
+ /** Anchor to attach when posting (set by highlight-to-comment). */
639
+ pendingAnchor: ThreadAnchor | null;
640
+ /** Clear the pending anchor (e.g. user dismisses the highlight). */
641
+ onClearAnchor: () => void;
642
+ /** Submit the new thread. */
643
+ onSubmit: (data: {
644
+ title: string;
645
+ content: string;
646
+ isInternal: boolean;
647
+ anchor: ThreadAnchor | null;
648
+ attachmentIds: string[];
649
+ }) => Promise<void>;
650
+ /** Upload a file as an attachment to this task. Returns the new attachment row. */
651
+ onUpload: (file: File) => Promise<Attachment>;
652
+ /** Add a link/recording as an attachment. Returns the new attachment row. */
653
+ onAddLink: (url: string, name?: string) => Promise<Attachment>;
654
+ /** Whether the current user is allowed to post internal-only threads. */
655
+ isInternalUser: boolean;
656
+ /** When false, render the collapsed "Start a thread" button instead. */
657
+ open: boolean;
658
+ /** Open the composer (e.g. user clicks the start button). */
659
+ onOpen: () => void;
660
+ /** Close + reset the composer. */
661
+ onClose: () => void;
662
+ }
663
+ /**
664
+ * Composer for new threads. Renders a single button when collapsed; when
665
+ * open, renders an optional anchor strip, optional title input, body
666
+ * textarea with @mentions, attachment buttons + chips, internal toggle,
667
+ * and the Post button.
668
+ */
669
+ declare function ThreadComposer({ attachments, pendingAnchor, onClearAnchor, onSubmit, onUpload, onAddLink, isInternalUser, open, onOpen, onClose, }: ThreadComposerProps): react_jsx_runtime.JSX.Element;
670
+
671
+ interface ActivityListProps {
672
+ activity: ActivityEntry[];
673
+ }
674
+ /** Status-change activity log shown in the Activity tab of the threads panel. */
675
+ declare function ActivityList({ activity }: ActivityListProps): react_jsx_runtime.JSX.Element;
676
+
677
+ type ContextPillVariant = 'amber' | 'purple' | 'blue' | 'emerald' | 'neutral';
678
+ interface ContextPillProps {
679
+ variant: ContextPillVariant;
680
+ icon: React__default.ComponentType<{
681
+ className?: string;
682
+ strokeWidth?: number;
683
+ size?: number;
684
+ }>;
685
+ label: string;
686
+ onClick?: (e: React__default.MouseEvent) => void;
687
+ }
688
+ /** A small color-coded pill used on thread cards (highlights, attachments, etc.). */
689
+ declare function ContextPill({ variant, icon: IconC, label, onClick }: ContextPillProps): react_jsx_runtime.JSX.Element;
690
+
691
+ interface BubbleState {
692
+ /** X coordinate (page-relative, viewport center of the selection rect). */
693
+ x: number;
694
+ /** Y coordinate (page-relative, scroll-aware). */
695
+ y: number;
696
+ section: SectionKey;
697
+ snippet: string;
698
+ }
699
+ interface UseHighlightAnchorResult {
700
+ /** Transient bubble state — set when the user has selected text inside a [data-section] block. */
701
+ bubble: BubbleState | null;
702
+ /** Manually clear the bubble (e.g. after user clicks the Comment button). */
703
+ clearBubble: () => void;
704
+ /** The anchor pending placement on a new thread, or null. */
705
+ pendingAnchor: ThreadAnchor | null;
706
+ /** Promote the current bubble to a pendingAnchor (the new-thread composer should react to this). */
707
+ beginAnchoredThread: () => ThreadAnchor | null;
708
+ /** Clear `pendingAnchor` after the thread is posted or the composer is dismissed. */
709
+ clearPendingAnchor: () => void;
710
+ /** Scroll the anchored section into view and pulse it briefly. Pass to ThreadCard / ThreadDetailView. */
711
+ focusAnchor: (anchor: ThreadAnchor) => void;
712
+ }
713
+ /**
714
+ * Wires up highlight-to-comment selection behaviour. The consuming page
715
+ * needs to render the description sections with `data-section={sectionKey}`
716
+ * for the bubble + focusAnchor to find the right element. Pair this hook
717
+ * with `<HighlightBubble bubble={bubble} onComment={beginAnchoredThread} />`
718
+ * and pass `pendingAnchor` / `clearPendingAnchor` to `<ThreadsPanel>`.
719
+ *
720
+ * Bug #1 fix lives in `<HighlightBubble>`: the button uses
721
+ * onMouseDown.preventDefault so clicking it doesn't blow away the selection.
722
+ */
723
+ declare function useHighlightAnchor(): UseHighlightAnchorResult;
724
+
725
+ interface HighlightBubbleProps {
726
+ bubble: BubbleState | null;
727
+ /** Called when the user clicks the bubble to start a new anchored thread. */
728
+ onComment: () => void;
729
+ }
730
+ /**
731
+ * Floating "Comment" button shown above a text selection inside a
732
+ * `[data-section]` block. The page should render this once at top level
733
+ * and pass `bubble` / `onComment` from `useHighlightAnchor`.
734
+ *
735
+ * Bug #1 fix: onMouseDown.preventDefault keeps focus on the selection so
736
+ * the snippet survives until onClick reads it.
737
+ */
738
+ declare function HighlightBubble({ bubble, onComment }: HighlightBubbleProps): react_jsx_runtime.JSX.Element | null;
739
+
287
740
  interface TaskBoardService {
288
741
  listTasks(projectSlug: string, perColumn?: number): Promise<Record<string, ColumnResponse>>;
289
742
  listColumnTasks(projectSlug: string, statusKey: string, offset: number, limit: number): Promise<Task[]>;
@@ -296,6 +749,20 @@ interface TaskBoardService {
296
749
  addComment(taskId: string, data: CreateCommentPayload): Promise<Comment>;
297
750
  editComment(taskId: string, commentId: string, data: EditCommentPayload): Promise<Comment>;
298
751
  deleteComment(taskId: string, commentId: string): Promise<void>;
752
+ /** Update a top-level comment's title and/or thread_status. */
753
+ updateThread(taskId: string, threadId: string, data: UpdateThreadPayload): Promise<Comment>;
754
+ listQuestions(taskId: string): Promise<Question[]>;
755
+ createQuestion(taskId: string, data: CreateQuestionPayload): Promise<Question>;
756
+ updateQuestion(taskId: string, questionId: string, data: UpdateQuestionPayload): Promise<Question>;
757
+ deleteQuestion(taskId: string, questionId: string): Promise<void>;
758
+ addQuestionReply(taskId: string, questionId: string, data: CreateQuestionReplyPayload): Promise<{
759
+ id: string;
760
+ }>;
761
+ deleteQuestionReply(taskId: string, questionId: string, replyId: string): Promise<void>;
762
+ listAttachments(taskId: string): Promise<Attachment[]>;
763
+ uploadAttachment(taskId: string, file: File): Promise<Attachment>;
764
+ addLinkAttachment(taskId: string, data: CreateLinkAttachmentPayload): Promise<Attachment>;
765
+ deleteAttachment(taskId: string, attachmentId: string): Promise<void>;
299
766
  listProjects(): Promise<Project[]>;
300
767
  searchMentionUsers(query: string): Promise<MentionUser[]>;
301
768
  getNotificationCount(): Promise<number>;
@@ -320,6 +787,8 @@ interface TaskBoardConfig {
320
787
  tags?: TagConfig[];
321
788
  /** Base API path (defaults to '/api/v1/taskboard') */
322
789
  apiBasePath?: string;
790
+ /** Label shown on internal-only comment chips. Defaults to "Internal". */
791
+ internalLabel?: string;
323
792
  /** Callbacks */
324
793
  onTaskCreate?: (task: Task) => void;
325
794
  onTaskUpdate?: (task: Task) => void;
@@ -346,6 +815,7 @@ interface TaskBoardContextValue {
346
815
  columns: ColumnConfig[];
347
816
  priorities: PriorityConfig[];
348
817
  tags: TagConfig[];
818
+ internalLabel: string;
349
819
  config: TaskBoardConfig;
350
820
  features: Required<NonNullable<TaskBoardConfig['features']>>;
351
821
  }
@@ -399,11 +869,60 @@ declare function useShareLink(): {
399
869
  copyShareLink: (taskId: string, projectSlug: string) => void;
400
870
  };
401
871
 
872
+ interface UseTaskQuestionsResult {
873
+ questions: Question[];
874
+ loading: boolean;
875
+ /** Re-fetch from the server. Use after mutations to refresh state. */
876
+ refresh: () => Promise<void>;
877
+ /** Optimistic local replace (e.g. when the parent page re-fetches the whole task). */
878
+ setQuestions: (next: Question[]) => void;
879
+ createQuestion: (text: string) => Promise<void>;
880
+ updateQuestion: (questionId: string, text: string) => Promise<void>;
881
+ setStatus: (questionId: string, status: QuestionStatus) => Promise<void>;
882
+ deleteQuestion: (questionId: string) => Promise<void>;
883
+ addReply: (questionId: string, content: string) => Promise<void>;
884
+ deleteReply: (questionId: string, replyId: string) => Promise<void>;
885
+ }
886
+ /**
887
+ * Manage the structured Outstanding Questions list for a single task.
888
+ *
889
+ * The hook owns its own loading state and re-fetches after each mutation
890
+ * so the parent component can stay simple. Pass `initial` from the
891
+ * embedded `TaskDetailResponse.questions` to avoid an extra request on
892
+ * mount.
893
+ */
894
+ declare function useTaskQuestions(taskId: string | null, initial?: Question[]): UseTaskQuestionsResult;
895
+
896
+ interface UseTaskAttachmentsResult {
897
+ attachments: Attachment[];
898
+ loading: boolean;
899
+ /** Re-fetch from the server. */
900
+ refresh: () => Promise<void>;
901
+ setAttachments: (next: Attachment[]) => void;
902
+ /** Upload a single file (image or document). Returns the new attachment. */
903
+ uploadFile: (file: File) => Promise<Attachment>;
904
+ /** Add an external link / recording. Returns the new attachment. */
905
+ addLink: (payload: CreateLinkAttachmentPayload) => Promise<Attachment>;
906
+ /** Delete an attachment (and its underlying GCS object, if any). */
907
+ remove: (attachmentId: string) => Promise<void>;
908
+ }
909
+ /**
910
+ * Manage the attachments collection for a single task.
911
+ *
912
+ * Pass `initial` from the embedded `TaskDetailResponse.attachments` to
913
+ * avoid an extra request on mount.
914
+ */
915
+ declare function useTaskAttachments(taskId: string | null, initial?: Attachment[]): UseTaskAttachmentsResult;
916
+
402
917
  declare function getPriorityStyle(priority: string): PriorityConfig;
403
918
  declare function getTagStyle(tag: string): TagConfig;
404
919
  declare function getInitials(name: string): string;
920
+ /** Parse a date string, treating timezone-naive Mongo timestamps as UTC. */
921
+ declare function parseDate(dateStr: string): Date;
405
922
  declare function formatDate(dateStr: string): string;
406
923
  declare function formatDateTime(dateStr: string): string;
924
+ /** Build a short task ID label like "T-AB12CD" from a Mongo ObjectId. */
925
+ declare function formatTaskId(id: string): string;
407
926
  declare function getDescriptionPreview(desc: StructuredDescription | string | undefined): string;
408
927
  declare function hasDescription(desc: StructuredDescription | string | undefined): boolean;
409
928
  declare function getUserProjects(apps: string[], allProjects: {
@@ -414,6 +933,20 @@ declare function getUserProjects(apps: string[], allProjects: {
414
933
  name: string;
415
934
  }[];
416
935
 
936
+ /**
937
+ * Markdown ⇄ HTML round-trip helpers used by the WYSIWYG `MarkdownEditor`.
938
+ *
939
+ * Storage format: markdown (so existing readers, the backend, and previews
940
+ * keep working). Editing format: HTML in a contenteditable div, so users
941
+ * never see raw `**` / `- ` / `# ` syntax.
942
+ *
943
+ * Mentions stored as `@[Name](username)` round-trip through the
944
+ * `<span class="mention-pill" data-mention-username="...">@Name</span>`
945
+ * representation.
946
+ */
947
+ declare function mdToHtml(md: string): string;
948
+ declare function htmlToMd(html: string): string;
949
+
417
950
  declare const DEFAULT_COLUMNS: ColumnConfig[];
418
951
  declare const DEFAULT_PRIORITIES: PriorityConfig[];
419
952
  declare const PREDEFINED_TAGS: TagConfig[];
@@ -421,6 +954,8 @@ declare const DESCRIPTION_SECTIONS: DescriptionSectionConfig[];
421
954
  declare const EMPTY_DESCRIPTION: StructuredDescription;
422
955
  declare const POSITION_GAP = 1000;
423
956
  declare const DEFAULT_PAGE_SIZE = 10;
957
+ /** Default label shown on internal-only comments. Override via TaskBoardProvider's `internalLabel` prop. */
958
+ declare const DEFAULT_INTERNAL_LABEL = "Internal";
424
959
 
425
960
  interface IconProps {
426
961
  className?: string;
@@ -430,15 +965,39 @@ interface IconProps {
430
965
  declare const PlusIcon: React__default.FC<IconProps>;
431
966
  declare const XIcon: React__default.FC<IconProps>;
432
967
  declare const ChevronDownIcon: React__default.FC<IconProps>;
968
+ declare const ChevronLeftIcon: React__default.FC<IconProps>;
969
+ declare const ChevronRightIcon: React__default.FC<IconProps>;
970
+ declare const ArrowLeftIcon: React__default.FC<IconProps>;
971
+ declare const MoreVerticalIcon: React__default.FC<IconProps>;
972
+ declare const Share2Icon: React__default.FC<IconProps>;
433
973
  declare const MessageSquareIcon: React__default.FC<IconProps>;
434
974
  declare const KanbanIcon: React__default.FC<IconProps>;
435
975
  declare const LinkIcon: React__default.FC<IconProps>;
976
+ declare const Link2Icon: React__default.FC<IconProps>;
977
+ declare const ExternalLinkIcon: React__default.FC<IconProps>;
978
+ declare const ImageIcon: React__default.FC<IconProps>;
979
+ declare const FileTextIcon: React__default.FC<IconProps>;
436
980
  declare const CheckIcon: React__default.FC<IconProps>;
981
+ declare const CheckCircle2Icon: React__default.FC<IconProps>;
437
982
  declare const BellIcon: React__default.FC<IconProps>;
438
983
  declare const FilterIcon: React__default.FC<IconProps>;
439
984
  declare const PencilIcon: React__default.FC<IconProps>;
440
985
  declare const TrashIcon: React__default.FC<IconProps>;
441
986
  declare const LockIcon: React__default.FC<IconProps>;
442
987
  declare const FeedbackIcon: React__default.FC<IconProps>;
988
+ declare const HelpCircleIcon: React__default.FC<IconProps>;
989
+ declare const CornerUpLeftIcon: React__default.FC<IconProps>;
990
+ declare const RotateCcwIcon: React__default.FC<IconProps>;
991
+ declare const Bold: React__default.FC<IconProps>;
992
+ declare const Italic: React__default.FC<IconProps>;
993
+ declare const List: React__default.FC<IconProps>;
994
+ declare const ListOrdered: React__default.FC<IconProps>;
995
+ declare const Heading2: React__default.FC<IconProps>;
996
+ declare const Quote: React__default.FC<IconProps>;
997
+ declare const Code: React__default.FC<IconProps>;
998
+ declare const ChatDotsIcon: React__default.FC<IconProps>;
999
+ declare const HistoryIcon: React__default.FC<IconProps>;
1000
+ /** Sidebar-toggle icon used to collapse / re-open the threads panel. */
1001
+ declare const SidebarToggleIcon: React__default.FC<IconProps>;
443
1002
 
444
- export { type ActivityEntry, type ApiClient, type ApiClientConfig, BellIcon, BoardSkeleton, CheckIcon, ChevronDownIcon, type ColumnConfig, type ColumnResponse, type ColumnTotals, type ColumnUnreads, type Comment, type CreateCommentPayload, CreateTaskModal, type CreateTaskModalProps, type CreateTaskPayload, DEFAULT_COLUMNS, DEFAULT_PAGE_SIZE, DEFAULT_PRIORITIES, DESCRIPTION_SECTIONS, type DescriptionSectionConfig, EMPTY_DESCRIPTION, type EditCommentPayload, FeedbackIcon, FilterBar, type FilterBarProps, FilterIcon, KanbanColumn, type KanbanColumnProps, KanbanIcon, LinkIcon, LockIcon, MentionText, MentionTextarea, type MentionTextareaProps, type MentionUser, MessageSquareIcon, type Notification, NotificationBell, type NotificationBellProps, type NotificationCountResponse, POSITION_GAP, PREDEFINED_TAGS, PencilIcon, PlusIcon, PriorityBadge, type PriorityBadgeProps, type PriorityConfig, type Project, SkeletonCard, SkeletonPulse, type StructuredDescription, TagBadge, type TagBadgeProps, type TagConfig, type Task, TaskBoard, type TaskBoardConfig, type TaskBoardContextValue, type TaskBoardProps, TaskBoardProvider, type TaskBoardService, type TaskBoardUser, TaskCard, type TaskCardProps, TaskDetailPanel, type TaskDetailPanelProps, type TaskDetailResponse, type TasksByStatus, TrashIcon, type UpdateTaskPayload, UserAvatar, type UserAvatarProps, XIcon, createTaskBoardService, formatDate, formatDateTime, getDescriptionPreview, getInitials, getPriorityStyle, getTagStyle, getUserProjects, hasDescription, toDisplayText, toStoredText, useShareLink, useTaskActions, useTaskBoard, useTaskBoardContext, useTaskDetail };
1003
+ export { type ActivityEntry, ActivityList, type ActivityListProps, type ApiClient, type ApiClientConfig, ArrowLeftIcon, type Attachment, type AttachmentKind, AttachmentsSection, type AttachmentsSectionProps, BellIcon, BoardSkeleton, Bold, type BubbleState, ChatDotsIcon, CheckCircle2Icon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, Code, type ColumnConfig, type ColumnResponse, type ColumnTotals, type ColumnUnreads, type Comment, ContextPill, type ContextPillProps, type ContextPillVariant, CornerUpLeftIcon, type CreateCommentPayload, type CreateLinkAttachmentPayload, type CreateQuestionPayload, type CreateQuestionReplyPayload, CreateTaskModal, type CreateTaskModalProps, type CreateTaskPayload, DEFAULT_COLUMNS, DEFAULT_INTERNAL_LABEL, DEFAULT_PAGE_SIZE, DEFAULT_PRIORITIES, DESCRIPTION_SECTIONS, DescriptionSection, type DescriptionSectionConfig, type DescriptionSectionProps, EMPTY_DESCRIPTION, type EditCommentPayload, ExternalLinkIcon, FeedbackIcon, FileTextIcon, FilterBar, type FilterBarProps, FilterIcon, Heading2, HelpCircleIcon, HighlightBubble, type HighlightBubbleProps, HistoryIcon, ImageIcon, Italic, KanbanColumn, type KanbanColumnProps, KanbanIcon, Link2Icon, LinkIcon, List, ListOrdered, LockIcon, MarkdownEditor, type MarkdownEditorProps, MarkdownView, MentionText, MentionTextarea, type MentionTextareaProps, type MentionUser, MessageSquareIcon, MoreVerticalIcon, type Notification, NotificationBell, type NotificationBellProps, type NotificationCountResponse, OutstandingQuestionsSection, type OutstandingQuestionsSectionProps, POSITION_GAP, PREDEFINED_TAGS, PencilIcon, PlusIcon, PriorityBadge, type PriorityBadgeProps, type PriorityConfig, type Project, type Question, type QuestionReply, type QuestionStatus, Quote, RotateCcwIcon, type SectionKey, type SectionStatus, Share2Icon, SidebarToggleIcon, SkeletonCard, SkeletonPulse, type StructuredDescription, TagBadge, type TagBadgeProps, type TagConfig, type Task, TaskBoard, type TaskBoardConfig, type TaskBoardContextValue, type TaskBoardProps, TaskBoardProvider, type TaskBoardService, type TaskBoardUser, TaskCard, type TaskCardProps, TaskDetailPanel, type TaskDetailPanelProps, type TaskDetailResponse, TaskDetailView, type TaskDetailViewProps, type TasksByStatus, type Thread, type ThreadAnchor, ThreadCard, type ThreadCardProps, ThreadComposer, type ThreadComposerProps, ThreadDetailView, type ThreadDetailViewProps, type ThreadReply, type ThreadStatus, ThreadsPanel, type ThreadsPanelProps, TrashIcon, type UpdateQuestionPayload, type UpdateTaskPayload, type UpdateThreadPayload, type UseHighlightAnchorResult, type UseTaskAttachmentsResult, type UseTaskQuestionsResult, UserAvatar, type UserAvatarProps, XIcon, createTaskBoardService, deriveThreads, formatDate, formatDateTime, formatTaskId, getDescriptionPreview, getInitials, getPriorityStyle, getTagStyle, getUserProjects, hasDescription, htmlToMd, mdToHtml, parseDate, sectionLabel, timeAgo, toDisplayText, toStoredText, useHighlightAnchor, useShareLink, useTaskActions, useTaskAttachments, useTaskBoard, useTaskBoardContext, useTaskDetail, useTaskQuestions };