blixify-ui-web-mcp 0.1.26 → 0.1.27

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.
@@ -212,6 +212,26 @@ Hide and reorder are **per user** by default, kept in `persistViewState`. Wire
212
212
  the change is written to the model and handed to the consumer to persist for everyone — which
213
213
  is what a Space-backed DTW wants.
214
214
 
215
+ Persist the single change, not the list. `onChangeHide` passes the columns as shown plus the
216
+ edit that was made, and `onChangeDrag` passes the two column ids:
217
+
218
+ ```tsx
219
+ organise={{
220
+ onChangeHide: (_columns, change) => {
221
+ // INFO: apply the one edit to the stored columns — the on-screen list may carry a
222
+ // saved view, another tab's state or a copy older than the stored one
223
+ const next =
224
+ "showAll" in change
225
+ ? stored.map((each) => ({ ...each, hide: false }))
226
+ : stored.map((each) =>
227
+ each.id === change.columnId ? { ...each, hide: change.hide } : each,
228
+ );
229
+ handleSaveColumns(next);
230
+ },
231
+ onChangeDrag: (dragId, dropId) => handleSaveColumns(handleMove(stored, dragId, dropId)),
232
+ }}
233
+ ```
234
+
215
235
  ## Step-by-Step Setup
216
236
 
217
237
  **1. Install peer dependencies**
@@ -986,8 +1006,8 @@ Which field types can be edited from the list table, and how. Types not listed u
986
1006
  | `addColumnEnabled` | `boolean` | Shows an add-column button in the table header |
987
1007
  | `persistViewState` | `"persistent" \| "session"` | Remembers the list's sort, filters, hidden columns, page, rows per page, search and reading position per collection, so a Back navigation lands where the user left. `"persistent"` uses `localStorage`; `"session"` writes `sessionStorage` and reads it first, falling back to `localStorage`. The reading position is restored on mount and on returning from a record, not on a tab switch; with several lists on one page, only the list a record was last opened from restores (tracked in `blixify-dtw-persist-last-drill`). Covers table, list, grid, kanban and feeds; not map. List view only |
988
1008
  | `persistKey` | `string` | Keeps a separate saved view state for this instance: the storage key becomes `blixify-dtw-persist-<collectionId>-<persistKey>`. Saved state is read once, on mount, so give the component a React `key` that changes with `persistKey` |
989
- | `savedViews` | `boolean` | Shows a row of view tags under the tab bar: save the current filters, flex filters, sort, rows per page and columns under a name, reapply it, update it or delete it. Views belong to the tab they were saved on and are kept in `localStorage` under `blixify-dtw-views-<collectionId>[-<persistKey>][-<currentUser.userEmail>]`. Sort is held by the template while it is on, as with `persistViewState` |
990
- | `defaultSavedViews` | `DefaultSavedView[]` | Views provided by the app (`{ id?, name, tabId?, state }`), listed before the user's own and never deleted. Without `tabId` a view is listed on every tab. A field left out of `state` is reset to its default when the view is applied |
1009
+ | `savedViews` | `boolean` | Shows a row of view tags under the tab bar: save the current filters, flex filters, sort, rows per page and columns under a name, reapply it, update it or delete it. Views belong to the tab they were saved on and are kept in `localStorage` under `blixify-dtw-views-<collectionId>[-<persistKey>][-<currentUser.userEmail>]`. Sort is held by the template while it is on, as with `persistViewState`, and applying a view calls `onChangeSort`, `onChangePagination(0)` and `bareListHandleFilter` when those change. Each write re-reads storage first, so views saved in another browser tab are kept. With `onChangeHide` / `onChangeDrag`, a view's hides and column order stay on screen only: a toggle or reorder after applying one writes back the schema's columns plus that single change |
1010
+ | `defaultSavedViews` | `DefaultSavedView[]` | Views provided by the app (`{ id?, name, tabId?, state }`), listed before the user's own and never deleted. Without `tabId` a view is listed on every tab. A field left out of `state` is reset to its default when the view is applied. `id` must be unique; without one the view gets `default-<index>`, its position in the whole array |
991
1011
  | `groupBy` | `string` | Column ID to group list results by |
992
1012
  | `groupIdCollapsible` | `{ [key: string]: boolean }` | Initial collapsed/expanded state per group ID |
993
1013
  | `sortGroupByAlphabetically` | `boolean` | Sorts group-by values alphabetically |
@@ -995,7 +1015,7 @@ Which field types can be edited from the list table, and how. Types not listed u
995
1015
  | `moreSettings` | `{ text: string; onClick: () => void }[]` | Extra items appended to the "more" dropdown menu |
996
1016
  | `exportSettings` | `ExportSettings` | See Export Settings below |
997
1017
  | `onChangeSort` | `(value: Sort) => void` | Called when the user changes the sort |
998
- | `onChangeHide` | `(value: DataStructure[]) => void` | Called when a column is hidden from the view settings panel. Providing it switches the panel to schema mode — the hide is written to the model for everyone instead of being this user's own |
1018
+ | `onChangeHide` | `(value: DataStructure[], change: HideChange) => void` | Called when a column is hidden or shown from the view settings panel. Providing it switches the panel to schema mode — the hide is written to the model for everyone instead of being this user's own. `change` is `{ columnId, hide }` for one column or `{ showAll: true }`; persist that rather than `value` |
999
1019
  | `onChangeDrag` | `(dragId: string, dropId: string, newHeader: TableHeader[]) => void` | Called after a column is reordered in the view settings panel, so the consumer can persist the order |
1000
1020
  | `onChangeTabs` | `(value: ExtendedTabs[]) => void` | Called when tabs are added/removed/reordered |
1001
1021
  | `onChangePagination` | `(page: number) => void` | Called when the page changes |
@@ -9,6 +9,13 @@ import { Button } from "../../action/button";
9
9
  import { Loading } from "../../display/loading";
10
10
  import { DataWrapperTestSchema } from "./dataWrapper.fixtures";
11
11
  import { DataTemplateWrapper } from "./index";
12
+ import {
13
+ STORY_API_ENDPOINT,
14
+ STORY_NAMES,
15
+ handleBuildStoryRecords,
16
+ storyWorkspace,
17
+ useStoryBackend,
18
+ } from "./storyBackend.fixtures";
12
19
  import { MarkdownInput } from "../../input/markdown";
13
20
 
14
21
  const meta = {
@@ -24,21 +31,23 @@ const meta = {
24
31
 
25
32
  export default meta;
26
33
 
27
- // INFO: Story to test imageTagToggleMode + imageHideEnabled across 3 role views:
28
- // - Installer: no props, sees all photos
29
- // - Customer: imageHideEnabled=true, hidden photos filtered out in read view
30
- // - PE: imageTagToggleMode=true, eye icon to toggle hidden flag, read shows all
31
-
34
+ // INFO: the e2e CRUD spec answers this story's requests with page.route, so the
35
+ // story backend stands aside under Playwright
32
36
  export const DataWrapperTest = () => {
33
- const [mount, setMount] = useState<boolean>(false);
37
+ const mount = useStoryBackend({
38
+ collections: {
39
+ "dtw-test-collection": handleBuildStoryRecords(12, (index) => ({
40
+ _id: `checklist-${index}`,
41
+ clientName: STORY_NAMES[index % STORY_NAMES.length],
42
+ clientPhone: `01${2000000 + index * 1379}`,
43
+ address: `${index} Jalan Contoh, 50450 Kuala Lumpur`,
44
+ })),
45
+ },
46
+ yieldToE2E: true,
47
+ });
34
48
  const dataRefreshRef: any = useRef(null);
35
49
  const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
36
50
  const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
37
- const dtwBaseUrl = "http://localhost:3000";
38
-
39
- useEffect(() => {
40
- setMount(true);
41
- }, []);
42
51
 
43
52
  // INFO: no second handleRefresh effect — ReadQueryHoc auto-fetches on mount.
44
53
  // A manual refresh here only causes a visible double-loading flash and pads
@@ -58,8 +67,8 @@ export const DataWrapperTest = () => {
58
67
  <div className="font-semibold mb-1 text-gray-900">DTW Test</div>
59
68
  <div className="text-sm font-light mb-4 text-gray-500">
60
69
  Minimal 3-field DataTemplateWrapper story used by the e2e CRUD spec.
61
- Validates create, read, update, and delete against the configured
62
- backend without optional schema fields blocking submission.
70
+ Create, read, update and delete run against the in-memory story backend,
71
+ with no required fields blocking submission.
63
72
  </div>
64
73
  <DataTemplateWrapper
65
74
  ref={dataRefreshRef}
@@ -68,15 +77,7 @@ export const DataWrapperTest = () => {
68
77
  id={selectedId}
69
78
  collectionId={"dtw-test-collection"}
70
79
  schema={DataWrapperTestSchema}
71
- workspace={{
72
- apiEndpointType: "mongoDB",
73
- apiEndpoint: `${dtwBaseUrl}/api/data`,
74
- apiEndpointToken: "",
75
- apiUserToken: "",
76
- assetEndpoint:
77
- "https://firebasestorage.googleapis.com/v0/b/demo-bucket.firebasestorage.app/o/",
78
- assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
79
- }}
80
+ workspace={storyWorkspace()}
80
81
  edit={{
81
82
  allEditable: true,
82
83
  disabledUpdate: false,
@@ -600,9 +601,9 @@ export const DTWEmbeddedInMarkdown = () => {
600
601
  A real DataTemplateWrapper rendered inside a note through
601
602
  MarkdownInput&apos;s renderEmbed, with the flags Space sets on an embed:
602
603
  bareAddColumns, bareFlatTable, listAddable, listEditable and readDrawer.
603
- Add a column from the header, add a row inline, then check the header and
604
- the rows still line up and that no cell reads &quot;div&quot;. New rows
605
- are stored as drafts, which is what puts a React element in a cell.
604
+ Add a column from the header, add a row inline, then check the header
605
+ and the rows still line up and that no cell reads &quot;div&quot;. New
606
+ rows are stored as drafts, which is what puts a React element in a cell.
606
607
  </div>
607
608
  <MarkdownInput
608
609
  id="notes"
@@ -624,7 +625,7 @@ export const DTWEmbeddedInMarkdown = () => {
624
625
  // listObjectTableView is what selects the table; leave it off and the field
625
626
  // renders as a List.
626
627
 
627
- const LIST_OBJECT_ENDPOINT = "http://localhost:3000/api/data";
628
+ const LIST_OBJECT_ENDPOINT = STORY_API_ENDPOINT;
628
629
  const LIST_OBJECT_COLLECTION = "dtw-list-object-table";
629
630
 
630
631
  // INFO: tabIds on purpose — the action pill only renders its Previous/Next
@@ -701,7 +702,10 @@ export const DTWListObjectTable = () => {
701
702
  .onPost(`${LIST_OBJECT_ENDPOINT}/${LIST_OBJECT_COLLECTION}/get`)
702
703
  .reply((config) => {
703
704
  const body = handleParseBody(config);
704
- return [200, { data: store.find((row) => row._id === body.id) ?? null }];
705
+ return [
706
+ 200,
707
+ { data: store.find((row) => row._id === body.id) ?? null },
708
+ ];
705
709
  });
706
710
 
707
711
  mock
@@ -709,7 +713,8 @@ export const DTWListObjectTable = () => {
709
713
  .reply((config) => {
710
714
  const body = handleParseBody(config);
711
715
  const index = store.findIndex((row) => row._id === body.id);
712
- if (index >= 0) store[index] = { ...store[index], ...(body.data ?? {}) };
716
+ if (index >= 0)
717
+ store[index] = { ...store[index], ...(body.data ?? {}) };
713
718
  return [200, { success: body.id ?? true }];
714
719
  });
715
720