blixify-ui-web-mcp 0.1.37 → 0.1.38
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.
|
@@ -626,7 +626,47 @@ Use `organise.additionalQuery` to add filter controls above the list. Each entry
|
|
|
626
626
|
|
|
627
627
|
### With activity log (comments + audit trail)
|
|
628
628
|
|
|
629
|
-
Enable `edit.activityLog` to
|
|
629
|
+
Enable `edit.activityLog` to open the activity panel from list and read views. It uses the new feeds and pagination UI, an inline comment composer, editable comments, confirmed deletion, and before/after audit values. It follows `organise.darkMode`, including when opened from a legacy table.
|
|
630
|
+
|
|
631
|
+
In the modern table (`bareNewDTW`), the outlined chat bubble at the right of the first visible column opens that data's Comments first without navigating away. The column is pinned by default when no left pins are configured; mobile cards put the icon beside the title. Read views use the same outlined chat bubble with an accessible Comments and activity label. The collection-wide clock opens Logs first. Both entry points retain both enabled tabs, falling back to the available tab if only one is enabled.
|
|
632
|
+
|
|
633
|
+
Audit entries summarize the actor and number of changed fields, with a compact Field / Before / After comparison. Only three changed fields show initially; use Show all changes for the rest and Show more for long values.
|
|
634
|
+
|
|
635
|
+
Collection-wide activity is labelled **All data** and has a searchable **Filter by data** picker (searches data titles with a 400 ms debounce, up to 20 matches). It filters both comments and logs. Every entry displays its parent record title; click it to open the record. The row-specific modal header also shows the title. Missing/deleted/inaccessible records retain an ID fallback without a navigation link; collection-level comments are labelled Collection discussion. Posting while a data filter is selected attaches the comment to that record.
|
|
636
|
+
|
|
637
|
+
Use `edit.activityLog.recordTitleColumn` to choose the title/search field. Otherwise DTW uses `organise.breadcrumbAttributeId`, a `title`/`name` field, or the first string field. Title lookups and searches use the parent collection's connection, credentials and `bareListFilterQuery` scope. Backend authorization still governs access. A pending draft prompts before switching record scope; pending notification retries disable that switch.
|
|
638
|
+
|
|
639
|
+
Mention choices come from the existing `devSettings.organisationMembers` (`{ key, label, secondaryLabel? }[]`). Type `@` and select a person by keyboard or touch. A manually typed name does not identify a recipient. Old comments display without migration.
|
|
640
|
+
|
|
641
|
+
Comments retain `comment`, `userId`, `collectionId`, `itemId`, and `createdAt`. New comments additionally store `mentions` (`{ userId, label, start, end }[]`) and `mentionedUserIds`. Editing preserves the original author and creation time and adds `updatedAt`. Offsets refer to the saved comment string; editing away a selected mention removes that identity.
|
|
642
|
+
|
|
643
|
+
To send pings, provide `edit.activityLog.onMention`. It runs **after a successful save**, for newly mentioned users excluding the current user. Without it, mentions are saved but notifications are not sent. The consuming app owns delivery and record links:
|
|
644
|
+
|
|
645
|
+
```tsx
|
|
646
|
+
devSettings={{
|
|
647
|
+
organisationMembers: [
|
|
648
|
+
{ key: "user-1", label: "Alex Morgan" },
|
|
649
|
+
{ key: "user-2", label: "Sam Lee", secondaryLabel: "Design" },
|
|
650
|
+
],
|
|
651
|
+
}}
|
|
652
|
+
edit={{
|
|
653
|
+
activityLog: {
|
|
654
|
+
enableComments: true,
|
|
655
|
+
commentCollectionId: "tasks-comments",
|
|
656
|
+
enableLogs: true,
|
|
657
|
+
logsCollectionId: "tasks-logs",
|
|
658
|
+
currentUserId: "user-1",
|
|
659
|
+
onMention: async (event) => {
|
|
660
|
+
// Your authenticated notification integration. Throw if delivery fails.
|
|
661
|
+
await sendCommentNotifications(event);
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
}}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
`ActivityMentionEvent` is exported from `data/dataTemplate/dataStateModel`. It contains `commentId`, `commentCollectionId`, `collectionId`, `itemId`, `comment`, `userId`, and `mentionedUserIds`. A failed callback displays a retry action that repeats only notification delivery, never the comment write. The handler must be idempotent per comment ID and recipient, validate recipients' access server-side, and use trusted server-side identity. UI props and mention IDs are not an authorization boundary. Existing backend access controls continue to govern comment creation, editing, and deletion.
|
|
668
|
+
|
|
669
|
+
The panel keeps DataTemplateWrapper for fetching and paging, and uses the shared WriteQuery transport for composer mutations, forwarding configured API and user tokens. Custom comment-collection schemas must accept the added mention fields. No notification endpoint is assumed by the library.
|
|
630
670
|
|
|
631
671
|
```tsx
|
|
632
672
|
<DataTemplate
|
|
@@ -973,6 +1013,7 @@ Which field types can be edited from the list table, and how. Types not listed u
|
|
|
973
1013
|
| `logQuery` | `Query[]` | Additional queries to filter logs |
|
|
974
1014
|
| `userAPIEndpoint` | `string` | URL returning `{ data: { name: string, ...rest } }` — used to resolve user names in comments/logs |
|
|
975
1015
|
| `currentUserId` | `string` | Current user's record ID — required when `enableComments` is `true` |
|
|
1016
|
+
| `onMention` | `(event: ActivityMentionEvent) => void \| Promise<void>` | Optional notification handoff after saving; rejects to show a notification-only retry |
|
|
976
1017
|
|
|
977
1018
|
### Organise
|
|
978
1019
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import MockAdapter from "axios-mock-adapter";
|
|
4
|
+
import { ActivityModal } from "./activityModal";
|
|
5
|
+
import { DataStructure, DevSettings } from "./model";
|
|
6
|
+
import { DataTemplateWrapper } from "../dataWrapper";
|
|
7
|
+
import { ActivityLog } from "./dataStateModel";
|
|
8
|
+
import { handleApplyStoryQueries } from "../dataWrapper/storyBackend.fixtures";
|
|
9
|
+
|
|
10
|
+
const meta = { title: "Data/Activity Panel" };
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
const settings: DevSettings = {
|
|
14
|
+
server: "mongoServer",
|
|
15
|
+
dbEndpoint: "https://activity.blixify.invalid/data",
|
|
16
|
+
dbAPIToken: "",
|
|
17
|
+
dbUserToken: "",
|
|
18
|
+
schemaEndpoint: "",
|
|
19
|
+
imageEndpoint: "",
|
|
20
|
+
organisationMembers: [
|
|
21
|
+
{ key: "alex", label: "Alex Morgan", secondaryLabel: "Product" },
|
|
22
|
+
{ key: "sam", label: "Sam Lee", secondaryLabel: "Design" },
|
|
23
|
+
{ key: "jordan", label: "Jordan Rivera", secondaryLabel: "Engineering" },
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const taskSchema: DataStructure[] = [
|
|
28
|
+
{ id: "title", name: "Task", type: "string" },
|
|
29
|
+
{
|
|
30
|
+
id: "status",
|
|
31
|
+
name: "Status",
|
|
32
|
+
type: "list",
|
|
33
|
+
listSelection: ["In progress", "In review", "Done"],
|
|
34
|
+
listSelectionColour: ["blue", "yellow", "green"],
|
|
35
|
+
},
|
|
36
|
+
{ id: "owner", name: "Owner", type: "person" },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const ActivityDemo = ({
|
|
40
|
+
darkMode = false,
|
|
41
|
+
logsOnly = false,
|
|
42
|
+
integrated = false,
|
|
43
|
+
}: {
|
|
44
|
+
darkMode?: boolean;
|
|
45
|
+
logsOnly?: boolean;
|
|
46
|
+
integrated?: boolean;
|
|
47
|
+
}) => {
|
|
48
|
+
const [ready, setReady] = useState(false);
|
|
49
|
+
const [failPings, setFailPings] = useState(false);
|
|
50
|
+
const [recipients, setRecipients] = useState("");
|
|
51
|
+
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
52
|
+
const [selectedId, setSelectedId] = useState("");
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const rows: Record<string, any[]> = {
|
|
55
|
+
tasks: [
|
|
56
|
+
{
|
|
57
|
+
_id: "task-1",
|
|
58
|
+
title: "Review activity panel",
|
|
59
|
+
status: "In review",
|
|
60
|
+
owner: "sam",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
_id: "task-2",
|
|
64
|
+
title: "Check mobile layout",
|
|
65
|
+
status: "In progress",
|
|
66
|
+
owner: "jordan",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
"tasks-comments": [
|
|
70
|
+
{
|
|
71
|
+
_id: "comment-mobile",
|
|
72
|
+
collectionId: "tasks",
|
|
73
|
+
itemId: "task-2",
|
|
74
|
+
userId: "jordan",
|
|
75
|
+
comment: "Please check this task on your phone.",
|
|
76
|
+
createdAt: "2026-09-28T09:00:00Z",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
_id: "comment-1",
|
|
80
|
+
collectionId: "tasks",
|
|
81
|
+
itemId: "task-1",
|
|
82
|
+
userId: "sam",
|
|
83
|
+
comment:
|
|
84
|
+
"The updated layout is ready for review.\nThe mobile view keeps the composer within reach.",
|
|
85
|
+
createdAt: "2026-09-28T10:30:00Z",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
_id: "comment-2",
|
|
89
|
+
collectionId: "tasks",
|
|
90
|
+
itemId: "task-1",
|
|
91
|
+
userId: "alex",
|
|
92
|
+
comment: "Thanks @Sam Lee — I will check the spacing today.",
|
|
93
|
+
mentions: [{ userId: "sam", label: "Sam Lee", start: 7, end: 15 }],
|
|
94
|
+
mentionedUserIds: ["sam"],
|
|
95
|
+
createdAt: "2026-09-28T11:00:00Z",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
"tasks-logs": [
|
|
99
|
+
{
|
|
100
|
+
_id: "log-1",
|
|
101
|
+
collectionId: "tasks",
|
|
102
|
+
itemId: "task-1",
|
|
103
|
+
userId: "alex",
|
|
104
|
+
type: "update",
|
|
105
|
+
createdAt: "2026-09-28T11:05:00Z",
|
|
106
|
+
values: [
|
|
107
|
+
{
|
|
108
|
+
columnId: "status",
|
|
109
|
+
oldValue: "In progress",
|
|
110
|
+
newValue: "In review",
|
|
111
|
+
},
|
|
112
|
+
{ columnId: "priority", oldValue: "Normal", newValue: "High" },
|
|
113
|
+
{ columnId: "owner", oldValue: "Alex Morgan", newValue: "Sam Lee" },
|
|
114
|
+
{
|
|
115
|
+
columnId: "notes",
|
|
116
|
+
oldValue: "Review pending",
|
|
117
|
+
newValue: "The activity panel is ready for review. ".repeat(12),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
columnId: "dueDate",
|
|
121
|
+
oldValue: "2026-09-29",
|
|
122
|
+
newValue: "2026-09-30",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
_id: "log-2",
|
|
128
|
+
collectionId: "tasks",
|
|
129
|
+
itemId: "task-1",
|
|
130
|
+
userId: "jordan",
|
|
131
|
+
type: "create",
|
|
132
|
+
createdAt: "2026-09-27T09:00:00Z",
|
|
133
|
+
values: [{ columnId: "title", newValue: "Review activity panel" }],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
const mock = new MockAdapter(axios);
|
|
138
|
+
mock
|
|
139
|
+
.onPost(/^https:\/\/activity\.blixify\.invalid\/data\//)
|
|
140
|
+
.reply((request) => {
|
|
141
|
+
const [collection, action] = request.url!.split("/").slice(-2);
|
|
142
|
+
const body = JSON.parse(request.data);
|
|
143
|
+
if (action === "get")
|
|
144
|
+
return [
|
|
145
|
+
200,
|
|
146
|
+
{ data: rows[collection].find((row) => row._id === body.id) },
|
|
147
|
+
];
|
|
148
|
+
if (action === "list") {
|
|
149
|
+
const data = handleApplyStoryQueries(
|
|
150
|
+
rows[collection] ?? [],
|
|
151
|
+
body.query,
|
|
152
|
+
).sort((a, b) =>
|
|
153
|
+
String(b.createdAt ?? "").localeCompare(String(a.createdAt ?? "")),
|
|
154
|
+
);
|
|
155
|
+
return [
|
|
156
|
+
200,
|
|
157
|
+
{
|
|
158
|
+
data: data.slice(
|
|
159
|
+
body.cursor ?? 0,
|
|
160
|
+
(body.cursor ?? 0) + (body.limit ?? 10),
|
|
161
|
+
),
|
|
162
|
+
count: data.length,
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
if (action === "create") rows[collection].push(body.data);
|
|
167
|
+
if (action === "update")
|
|
168
|
+
rows[collection] = rows[collection].map((row) =>
|
|
169
|
+
row._id === body.id ? { ...row, ...body.data } : row,
|
|
170
|
+
);
|
|
171
|
+
if (action === "delete")
|
|
172
|
+
rows[collection] = rows[collection].filter(
|
|
173
|
+
(row) => row._id !== body.id,
|
|
174
|
+
);
|
|
175
|
+
return [200, { success: true }];
|
|
176
|
+
});
|
|
177
|
+
setReady(true);
|
|
178
|
+
return () => mock.restore();
|
|
179
|
+
}, []);
|
|
180
|
+
const activityLog: ActivityLog = {
|
|
181
|
+
enableComments: !logsOnly,
|
|
182
|
+
commentCollectionId: "tasks-comments",
|
|
183
|
+
enableLogs: true,
|
|
184
|
+
logsCollectionId: "tasks-logs",
|
|
185
|
+
currentUserId: "alex",
|
|
186
|
+
onMention: async (event) => {
|
|
187
|
+
if (failPings) throw new Error("Demo notification failure");
|
|
188
|
+
setRecipients(event.mentionedUserIds.join(", "));
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
return (
|
|
192
|
+
<div
|
|
193
|
+
className={`mx-auto max-w-3xl rounded-2xl p-3 sm:p-6 ${darkMode ? "bg-gray-900 text-gray-100" : "bg-gray-50 text-gray-800"}`}
|
|
194
|
+
>
|
|
195
|
+
<h1 className="mb-4 text-lg font-semibold">
|
|
196
|
+
{integrated
|
|
197
|
+
? "DTW · Tasks with comments"
|
|
198
|
+
: "Activity · Review activity panel"}
|
|
199
|
+
</h1>
|
|
200
|
+
{integrated && (
|
|
201
|
+
<p className="mb-4 text-sm text-gray-500">
|
|
202
|
+
Click the chat icon beside a task for its comments and audit history,
|
|
203
|
+
or open the task and click its chat bubble. The list clock opens logs first. Tasks have separate conversations. All
|
|
204
|
+
data and notifications are simulated; refreshing resets the demo.
|
|
205
|
+
Audit entries are seeded examples.
|
|
206
|
+
</p>
|
|
207
|
+
)}
|
|
208
|
+
{!logsOnly && (
|
|
209
|
+
<label className="mb-4 flex items-center gap-2 text-sm">
|
|
210
|
+
<input
|
|
211
|
+
type="checkbox"
|
|
212
|
+
checked={failPings}
|
|
213
|
+
onChange={(event) => setFailPings(event.target.checked)}
|
|
214
|
+
/>
|
|
215
|
+
Simulate notification failure
|
|
216
|
+
</label>
|
|
217
|
+
)}
|
|
218
|
+
{recipients && (
|
|
219
|
+
<p role="status" className="mb-3 text-sm">
|
|
220
|
+
Demo notification recipients: {recipients}
|
|
221
|
+
</p>
|
|
222
|
+
)}
|
|
223
|
+
{ready &&
|
|
224
|
+
(integrated ? (
|
|
225
|
+
<DataTemplateWrapper
|
|
226
|
+
title="Tasks"
|
|
227
|
+
type={viewType}
|
|
228
|
+
id={selectedId}
|
|
229
|
+
collectionId="tasks"
|
|
230
|
+
schema={taskSchema}
|
|
231
|
+
workspace={{
|
|
232
|
+
apiEndpoint: settings.dbEndpoint,
|
|
233
|
+
apiEndpointType: "mongoDB",
|
|
234
|
+
}}
|
|
235
|
+
devSettings={settings}
|
|
236
|
+
edit={{
|
|
237
|
+
allEditable: false,
|
|
238
|
+
createBtn: false,
|
|
239
|
+
deleteBtn: false,
|
|
240
|
+
disabledUpdate: true,
|
|
241
|
+
activityLog,
|
|
242
|
+
}}
|
|
243
|
+
bareSettings={{ bareNewDTW: true, bareOffline: false }}
|
|
244
|
+
organise={{ breadcrumbType: "button", darkMode }}
|
|
245
|
+
handleNavigate={(type, id) => {
|
|
246
|
+
setViewType(type as "list" | "read" | "update");
|
|
247
|
+
setSelectedId(id ?? "");
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
) : (
|
|
251
|
+
<ActivityModal
|
|
252
|
+
title="Tasks"
|
|
253
|
+
collectionId="tasks"
|
|
254
|
+
dataId="task-1"
|
|
255
|
+
devSettings={settings}
|
|
256
|
+
darkMode={darkMode}
|
|
257
|
+
activityLog={activityLog}
|
|
258
|
+
/>
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export const CommentsAndLogs = () => <ActivityDemo />;
|
|
265
|
+
export const DarkMode = () => <ActivityDemo darkMode />;
|
|
266
|
+
export const LogsOnly = () => <ActivityDemo logsOnly />;
|
|
267
|
+
export const WithDataTemplateWrapper = () => <ActivityDemo integrated />;
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Tabular display with a List-shaped mobile breakpoint. The architectural key: **`mobileView=true` delegates row rendering to `NewListRow`**, so the same tap-rhythm and avatar/title/trailing geometry shows up across NewList, NewFeeds, and NewTable mobile.
|
|
4
4
|
|
|
5
|
+
## Row activity
|
|
6
|
+
|
|
7
|
+
Pass `onRowActivity={(id) => ...}` to show a keyboard-accessible chat button at the right of the first visible cell (beside the title on mobile cards). It stops row navigation and does not enter inline editing. When no left pins are configured, the first ungrouped column is pinned automatically; existing pin configurations are preserved. DTW wires this callback automatically when `edit.activityLog` is enabled with `bareNewDTW`.
|
|
8
|
+
|
|
5
9
|
## Mobile column flags
|
|
6
10
|
|
|
7
11
|
Columns expose three optional booleans that map them to the `NewListItem` shape:
|