@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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @emberai-engg/task-board
2
2
 
3
- Reusable Kanban task board component with built-in create/detail UI.
3
+ Reusable Kanban task board component with built-in create/detail UI, threaded
4
+ discussions with highlight-to-comment, structured outstanding questions, file
5
+ attachments backed by Google Cloud Storage, and a WYSIWYG markdown editor.
4
6
 
5
7
  ## Installation
6
8
 
@@ -8,9 +10,21 @@ Reusable Kanban task board component with built-in create/detail UI.
8
10
  npm install @emberai-engg/task-board
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ Then import the bundled stylesheet once at app start (e.g. in `app/layout.tsx`):
12
14
 
13
- No render props needed. The package ships with a complete UI out of the box:
15
+ ```ts
16
+ import '@emberai-engg/task-board/styles.css';
17
+ ```
18
+
19
+ That's it. The stylesheet is self-contained — Tailwind utilities used by the
20
+ package are pre-compiled into it, so you don't need to add anything to your own
21
+ Tailwind config or `@source` paths. The bundled CSS deliberately omits the
22
+ Tailwind preflight so it won't trample your app's own resets.
23
+
24
+ ## Quick Start — list page only (slide-over detail)
25
+
26
+ The default `<TaskBoard>` ships with a built-in `<TaskDetailPanel>` slide-over
27
+ that opens when a task card is clicked.
14
28
 
15
29
  ```tsx
16
30
  import { TaskBoardProvider, TaskBoard } from '@emberai-engg/task-board';
@@ -23,17 +37,8 @@ function App() {
23
37
  return (
24
38
  <TaskBoardProvider
25
39
  apiClient={apiClient}
26
- user={{
27
- username: user.username,
28
- name: user.name,
29
- email: user.email,
30
- apps: user.apps,
31
- is_internal: user.is_internal,
32
- is_reviewer: user.is_reviewer,
33
- }}
34
- projects={[
35
- { slug: 'my-project', name: 'My Project' },
36
- ]}
40
+ user={user}
41
+ projects={[{ slug: 'my-project', name: 'My Project' }]}
37
42
  >
38
43
  <TaskBoard onShareFeedback={() => router.push('/feedback')} />
39
44
  </TaskBoardProvider>
@@ -41,14 +46,63 @@ function App() {
41
46
  }
42
47
  ```
43
48
 
44
- This gives you:
45
- - Kanban board with drag-and-drop
46
- - Built-in **CreateTaskModal** (two-column layout with title, structured description, priority, status, tags)
47
- - Built-in **TaskDetailPanel** (slide-over with properties grid, description sections, activity timeline, comments with @mentions)
48
- - Notification bell with polling
49
- - Tag filtering
50
- - Share links
51
- - Loading skeletons and empty states
49
+ ## Quick Start — list + dedicated detail route
50
+
51
+ For larger workflows (description sections, outstanding questions, attachments,
52
+ threads with highlight-to-comment), pair `<TaskBoard>` on the list route with
53
+ `<TaskDetailView>` on a separate detail route.
54
+
55
+ ```tsx
56
+ // app/task-board/page.tsx
57
+ function TaskBoardPage() {
58
+ const router = useRouter();
59
+ return (
60
+ <TaskBoardProvider apiClient={apiClient} user={user} projects={projects}>
61
+ <TaskBoard
62
+ onTaskOpen={(task) => router.push(`/task-board/${task.id}?project=${task.project_slug}`)}
63
+ />
64
+ </TaskBoardProvider>
65
+ );
66
+ }
67
+
68
+ // app/task-board/[taskId]/page.tsx
69
+ function TaskDetailPage({ params }: { params: { taskId: string } }) {
70
+ const router = useRouter();
71
+ return (
72
+ <TaskBoardProvider apiClient={apiClient} user={user} projects={projects}>
73
+ <TaskDetailView
74
+ taskId={params.taskId}
75
+ onBack={() => router.push('/task-board')}
76
+ onNavigateToTask={(id, slug) => router.push(`/task-board/${id}?project=${slug}`)}
77
+ onDeleted={() => router.push('/task-board')}
78
+ />
79
+ </TaskBoardProvider>
80
+ );
81
+ }
82
+ ```
83
+
84
+ When `onTaskOpen` is set, `<TaskBoard>` skips its built-in slide-over panel —
85
+ the consumer owns navigation.
86
+
87
+ ## What the detail page gives you
88
+
89
+ - **Inline-editable title**, click-to-edit
90
+ - **Status / priority / share / delete** in the header
91
+ - **Four description sections** (Problem, User Story, Proposed Behavior,
92
+ Acceptance Criteria), each with a Draft/Approved toggle
93
+ - **WYSIWYG markdown editor** — bold/italic, H2, bullet/numbered lists with
94
+ multi-line support and auto-incrementing numbers, blockquote, inline code,
95
+ @mentions. Stores markdown on disk via `mdToHtml`/`htmlToMd` round-trip.
96
+ - **Outstanding Questions** — structured list with awaiting/answered status,
97
+ per-question replies, mark-answered/reopen, author-only delete
98
+ - **Attachments** — three groups (Images / Files / Links & recordings), GCS
99
+ upload, signed read URLs, hover-to-delete
100
+ - **Threads panel** — Threads + Activity tabs, collapse toggle (persisted),
101
+ active/completed filter, per-thread title and complete/reopen, thread
102
+ composer with attachments and public/internal toggle
103
+ - **Highlight-to-comment** — select text in a description section to attach a
104
+ thread anchor; clicking the anchor pill scrolls back to the section and
105
+ pulses it
52
106
 
53
107
  ## Props / Config Reference
54
108
 
@@ -63,6 +117,7 @@ This gives you:
63
117
  | `priorities` | `PriorityConfig[]` | No | Priority levels |
64
118
  | `tags` | `TagConfig[]` | No | Predefined tags |
65
119
  | `apiBasePath` | `string` | No | API prefix (defaults to `/api/v1/taskboard`) |
120
+ | `internalLabel` | `string` | No | Label on internal-only comment chips. Defaults to `"Internal"`. |
66
121
  | `features` | `object` | No | Feature flags for enabling/disabling features |
67
122
  | `onTaskCreate` | `(task) => void` | No | Callback on task creation |
68
123
  | `onTaskUpdate` | `(task) => void` | No | Callback on task update |
@@ -76,52 +131,32 @@ This gives you:
76
131
  | `className` | `string` | CSS class for the outer container |
77
132
  | `headerActions` | `ReactNode` | Additional buttons in the header |
78
133
  | `onShareFeedback` | `() => void` | Callback for Share Feedback button. Hidden if omitted. |
79
- | `onTaskOpen` | `(task) => void` | Callback when a task is clicked |
80
- | `renderTaskDetail` | `function` | Override for task detail panel (built-in used if omitted) |
134
+ | `onTaskOpen` | `(task) => void` | When provided, the built-in slide-over panel is suppressed and the consumer owns navigation. |
135
+ | `renderTaskDetail` | `function` | Override for task detail panel (built-in slide-over used if omitted) |
81
136
  | `renderCreateTask` | `function` | Override for create task modal (built-in used if omitted) |
82
137
 
83
- ### Overriding built-in UI
84
-
85
- If you need custom create/detail UI, pass render props:
86
-
87
- ```tsx
88
- <TaskBoard
89
- renderCreateTask={({ projectSlug, defaultStatus, onClose, onCreate }) => (
90
- <MyCustomCreateModal ... />
91
- )}
92
- renderTaskDetail={({ task, onClose, onUpdate }) => (
93
- <MyCustomDetailPanel ... />
94
- )}
95
- />
96
- ```
138
+ ### `TaskDetailView`
97
139
 
98
- ## Using Individual Components
99
-
100
- ```tsx
101
- import {
102
- TaskCard, PriorityBadge, UserAvatar,
103
- CreateTaskModal, TaskDetailPanel,
104
- useTaskActions,
105
- } from '@emberai-engg/task-board';
106
-
107
- // Use hooks independently
108
- function MyCustomUI() {
109
- const { createTask, moveTask } = useTaskActions(tasks, setTasks, fetchTasks);
110
- // ...
111
- }
112
-
113
- // Use small components
114
- <PriorityBadge priority="high" />
115
- <UserAvatar name="John Smith" size="sm" showTooltip />
116
- ```
140
+ | Prop | Type | Description |
141
+ |------|------|-------------|
142
+ | `taskId` | `string` | Required. The task to show. |
143
+ | `onBack` | `() => void` | Callback for the back link. Use this for SPA routing. |
144
+ | `backHref` | `string` | href for the back link if `onBack` is omitted. |
145
+ | `breadcrumb` | `ReactNode` | Slot rendered above the main content (e.g. consumer breadcrumb bar). |
146
+ | `onDeleted` | `(task) => void` | Called after successful delete; consumer should navigate away. |
147
+ | `onNavigateToTask` | `(id, projectSlug) => void` | Provide to enable prev/next buttons. |
148
+ | `buildShareUrl` | `(task) => string` | Override the share URL. Defaults to `${origin}/task-board/${id}?project=${slug}`. |
117
149
 
118
150
  ## Hooks API
119
151
 
120
152
  | Hook | Purpose |
121
153
  |------|---------|
122
154
  | `useTaskBoard()` | Board state: projects, tasks, loading, pagination |
123
- | `useTaskActions(tasks, setTasks, fetchTasks)` | CRUD: create, update, delete, move tasks |
155
+ | `useTaskActions(...)` | CRUD: create, update, delete, move tasks |
124
156
  | `useTaskDetail(taskId)` | Single task: comments, activity, field updates |
157
+ | `useTaskQuestions(taskId, initial?)` | Outstanding Questions for a task: list + create / update / delete / reply |
158
+ | `useTaskAttachments(taskId, initial?)` | Attachments: list + uploadFile / addLink / remove |
159
+ | `useHighlightAnchor()` | Selection-driven anchor flow: bubble + pendingAnchor + focusAnchor |
125
160
  | `useShareLink()` | Copy shareable task URLs |
126
161
 
127
162
  ## Feature Flags
@@ -129,19 +164,49 @@ function MyCustomUI() {
129
164
  ```tsx
130
165
  <TaskBoardProvider
131
166
  features={{
132
- dragAndDrop: true, // Drag-and-drop between columns
133
- comments: true, // Comment system
134
- mentions: true, // @mention users
135
- notifications: true, // Notification bell
136
- internalComments: true, // Internal-only comments
137
- tags: true, // Tag system
138
- sharing: true, // Shareable task links
139
- filters: true, // Tag filtering
140
- unreadIndicators: true, // Unread dots on cards
167
+ dragAndDrop: true,
168
+ comments: true,
169
+ mentions: true,
170
+ notifications: true,
171
+ internalComments: true,
172
+ tags: true,
173
+ sharing: true,
174
+ filters: true,
175
+ unreadIndicators: true,
141
176
  }}
142
177
  />
143
178
  ```
144
179
 
180
+ ## Backwards-compatibility notes (v0.3 → v0.4)
181
+
182
+ - The `StructuredDescription.open_questions` field still exists and is preserved
183
+ on existing tasks, but is no longer rendered in the description editor — the
184
+ Outstanding Questions feature replaces it. Tasks created before v0.4 keep
185
+ their content; new tasks have an empty `open_questions` field.
186
+ - `Comment` gained optional `parent_id`, `title`, `thread_status`, `anchor`,
187
+ `attachment_ids`. Existing comments without these fields render the same as
188
+ before.
189
+
190
+ ## Backend
191
+
192
+ The `backend-reference/` folder contains a Python FastAPI reference implementation:
193
+
194
+ - `models/taskboard.py` — Pydantic models (Tasks / Comments / Threads /
195
+ Questions / Attachments + the `ThreadAnchor` schema)
196
+ - `api/taskboard.py` — full FastAPI router covering all v0.4 endpoints,
197
+ including the threads, questions, and attachments routes
198
+ - `services/gcs_storage.py` — Google Cloud Storage helper for file uploads.
199
+ Supports inline service-account env vars, `GOOGLE_APPLICATION_CREDENTIALS`,
200
+ and Application Default Credentials
201
+ - `services/config_snippet.py` — settings fields and sample `.env` values
202
+
203
+ Add `google-cloud-storage>=2.18.0` to `requirements.txt`. Without
204
+ `GCP_STORAGE_BUCKET` set, link attachments still work but image/file uploads
205
+ return a clean 503.
206
+
207
+ See [`backend-reference/README.md`](backend-reference/README.md) for the
208
+ adaptation guide.
209
+
145
210
  ## Development
146
211
 
147
212
  ```bash
@@ -151,10 +216,6 @@ npm run build # Production build
151
216
  npm test # Run tests
152
217
  ```
153
218
 
154
- ## Backend
155
-
156
- The `backend-reference/` folder contains a Python FastAPI reference implementation for the task board API. Copy it into your app's backend and adapt the auth and database setup to match your app. See [`backend-reference/README.md`](backend-reference/README.md) for details.
157
-
158
219
  ## License
159
220
 
160
221
  Private — internal use only.