From 639cac2e33e34609bdf136b089428f328a38f9e2 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 27 May 2026 16:48:52 +0200
Subject: [PATCH] fixes udb
---
config/env-dev.env | 2 +-
src/api/connectionApi.ts | 21 +-
.../FormGeneratorTree/FormGeneratorTree.tsx | 59 +--
.../__tests__/FormGeneratorTree.test.tsx | 32 +-
.../providers/FolderFileProvider.tsx | 13 -
.../FormGenerator/FormGeneratorTree/types.ts | 18 -
src/components/UnifiedDataBar/FilesTab.tsx | 2 -
src/components/UnifiedDataBar/SourcesTab.tsx | 7 +-
.../UnifiedDataBar/UdbSourcesProvider.tsx | 171 +++------
.../__tests__/UdbSourcesProvider.test.ts | 352 ++++++------------
10 files changed, 199 insertions(+), 478 deletions(-)
diff --git a/config/env-dev.env b/config/env-dev.env
index a66d513..77507d3 100644
--- a/config/env-dev.env
+++ b/config/env-dev.env
@@ -2,5 +2,5 @@
# Consumed by: Vite build (title) + SPA runtime (getApiBaseUrl / getAppName)
# Auth and secrets live on the gateway — never in frontend env.
-VITE_API_BASE_URL="http://localhost:8000/"
+VITE_API_BASE_URL="http://localhost:8000"
VITE_APP_NAME=PowerOn Nyla dev
diff --git a/src/api/connectionApi.ts b/src/api/connectionApi.ts
index d482948..0322c66 100644
--- a/src/api/connectionApi.ts
+++ b/src/api/connectionApi.ts
@@ -373,24 +373,9 @@ export async function getDataSourceCostEstimate(
});
}
-export interface PatchFlagResponse {
- sourceId: string;
- resetDescendantIds: string[];
- updatedAncestors: { id: string; [key: string]: any }[];
- [key: string]: any;
-}
-
-export async function patchDataSourceRagIndex(
- request: ApiRequestFunction,
- dataSourceId: string,
- ragIndexEnabled: boolean | null
-): Promise {
- return await request({
- url: `/api/datasources/${dataSourceId}/rag-index`,
- method: 'patch',
- data: { ragIndexEnabled }
- });
-}
+// Flag toggles (neutralize / scope / ragIndexEnabled) now go through the
+// generic UDB endpoint POST /api/udb/node/{key}/flag/{flag}; see
+// `UdbSourcesProvider` and the wiki UDB reference page.
// ============================================================================
// RAG INVENTORY
diff --git a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx
index 7582787..1b9aca7 100644
--- a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx
+++ b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx
@@ -568,7 +568,6 @@ export function FormGeneratorTree({
onSendToChat,
allowCreateFolder = true,
selectable = true,
- refreshAfterAction = false,
className,
embedMaxHeight,
hideRowActionButtons = false,
@@ -614,48 +613,28 @@ export function FormGeneratorTree({
);
- /** After a toggle, collect all currently visible node IDs and ask the
- * provider for their updated attributes. Patches only attribute fields
- * (neutralize, scope, ragIndexEnabled) on existing nodes — no structural
- * reload. Falls back to full refetch if provider doesn't implement
- * refreshAttributes. */
+ /** After a toggle, refetch children for root + all expanded parents so the
+ * backend-authoritative effective flag values are current. No attribute-only
+ * shortcut — the backend is the single source of truth (spec 2026-05-18). */
const _refreshVisibleAttributes = useCallback(async () => {
- if (provider.refreshAttributes) {
- const visibleIds = flatEntriesRef.current.map((e) => e.node.id);
- if (visibleIds.length === 0) return;
- const attrs = await provider.refreshAttributes(visibleIds);
- setNodes((prev) =>
- prev.map((n) => {
- const update = attrs.get(n.id);
- if (!update) return n;
- const patched: Partial = {};
- if (n.neutralize !== undefined && update.neutralize !== undefined) patched.neutralize = update.neutralize;
- if (n.scope !== undefined && update.scope !== undefined) patched.scope = update.scope;
- if (n.ragIndexEnabled !== undefined && update.ragIndexEnabled !== undefined) patched.ragIndexEnabled = update.ragIndexEnabled;
- if (Object.keys(patched).length === 0) return n;
- return { ...n, ...patched };
- }),
- );
- } else {
- const expandedList: (string | null)[] = [null, ...Array.from(expandedIds)];
- const fetched = await Promise.all(
- expandedList.map((p) => provider.loadChildren(p, ownership)),
- );
- const refetchedParents = new Set(expandedList.map((p) => p ?? '__null__'));
- setNodes((prev) => {
- const keepers = prev.filter((n) => {
- const key = n.parentId ?? '__null__';
- return !refetchedParents.has(key);
- });
- return [...keepers, ...fetched.flat()];
+ const expandedList: (string | null)[] = [null, ...Array.from(expandedIds)];
+ const fetched = await Promise.all(
+ expandedList.map((p) => provider.loadChildren(p, ownership)),
+ );
+ const refetchedParents = new Set(expandedList.map((p) => p ?? '__null__'));
+ setNodes((prev) => {
+ const keepers = prev.filter((n) => {
+ const key = n.parentId ?? '__null__';
+ return !refetchedParents.has(key);
});
- }
+ return [...keepers, ...fetched.flat()];
+ });
}, [expandedIds, provider, ownership]);
/** Wrap any async action with pending-state tracking so the tree can show
* a spinner over the corresponding button. Generic — no domain knowledge.
- * When `refreshAfterAction` is enabled, the spinner stays on until the
- * refreshed attributes have been written into state. */
+ * Always refetches all expanded parents after the action completes so the
+ * backend-authoritative values are rendered. */
const _runAction = useCallback(
async (nodeId: string, actionKey: string, fn: () => Promise | void) => {
setPendingActions((prev) => {
@@ -667,9 +646,7 @@ export function FormGeneratorTree({
});
try {
await fn();
- if (refreshAfterAction || provider.refreshAttributes) {
- await _refreshVisibleAttributes();
- }
+ await _refreshVisibleAttributes();
} finally {
setPendingActions((prev) => {
const next = new Map(prev);
@@ -681,7 +658,7 @@ export function FormGeneratorTree({
});
}
},
- [refreshAfterAction, _refreshVisibleAttributes],
+ [_refreshVisibleAttributes],
);
const _loadRoot = useCallback(async () => {
diff --git a/src/components/FormGenerator/FormGeneratorTree/__tests__/FormGeneratorTree.test.tsx b/src/components/FormGenerator/FormGeneratorTree/__tests__/FormGeneratorTree.test.tsx
index e1ad065..b5f9093 100644
--- a/src/components/FormGenerator/FormGeneratorTree/__tests__/FormGeneratorTree.test.tsx
+++ b/src/components/FormGenerator/FormGeneratorTree/__tests__/FormGeneratorTree.test.tsx
@@ -1106,19 +1106,15 @@ describe('FormGeneratorTree', () => {
});
// ---------------------------------------------------------------------------
- // refreshAfterAction (backend-authoritative mode)
+ // Always refetch after action (backend-authoritative, spec 2026-05-18)
// ---------------------------------------------------------------------------
- describe('refreshAfterAction', () => {
+ describe('refetch after action', () => {
it('refetches null + expanded parents after a flag toggle', async () => {
const user = userEvent.setup();
const provider = _createMockProvider([_ownFolder]);
render(
- ,
+ ,
);
await waitFor(() => {
@@ -1139,28 +1135,6 @@ describe('FormGeneratorTree', () => {
expect(newCalls.length).toBeGreaterThan(initialLoadCalls);
expect(newCalls.some(c => c[0] === null && c[1] === 'own')).toBe(true);
});
-
- it('does NOT refetch when refreshAfterAction is false (default)', async () => {
- const user = userEvent.setup();
- const provider = _createMockProvider([_ownFolder]);
- render();
-
- await waitFor(() => {
- expect(screen.getByText('My Folder')).toBeInTheDocument();
- });
-
- const initialLoadCalls = (provider.loadChildren as ReturnType).mock.calls.length;
-
- const neutralizeBtn = screen.getByTitle('Nicht neutralisiert');
- await user.click(neutralizeBtn);
-
- await waitFor(() => {
- expect(provider.patchNeutralize).toHaveBeenCalled();
- });
-
- const newCalls = (provider.loadChildren as ReturnType).mock.calls.length;
- expect(newCalls).toBe(initialLoadCalls);
- });
});
// ---------------------------------------------------------------------------
diff --git a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx
index 58f800d..f983a84 100644
--- a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx
+++ b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx
@@ -299,19 +299,6 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = {
);
},
- async refreshAttributes(ids: string[]) {
- const res = await api.post('/api/files/attributes', { ids });
- const raw: Record = res.data ?? {};
- const result = new Map();
- for (const [id, attrs] of Object.entries(raw)) {
- result.set(id, {
- neutralize: attrs.neutralize,
- scope: attrs.scope as ScopeValue | 'mixed',
- });
- }
- return result;
- },
-
getBatchActions(): TreeBatchAction[] {
return [
{
diff --git a/src/components/FormGenerator/FormGeneratorTree/types.ts b/src/components/FormGenerator/FormGeneratorTree/types.ts
index 39c34d6..b44cfd0 100644
--- a/src/components/FormGenerator/FormGeneratorTree/types.ts
+++ b/src/components/FormGenerator/FormGeneratorTree/types.ts
@@ -88,16 +88,6 @@ export interface TreeNodeProvider {
patchRagIndex?(ids: string[], ragIndexEnabled: boolean): Promise;
downloadNode?(node: TreeNode): Promise;
getBatchActions?(): TreeBatchAction[];
- /** After a toggle action, the tree collects all currently visible node IDs
- * and calls this method. The provider asks the backend for the current
- * attribute values (incl. mixed) of exactly those IDs. The tree then
- * patches only the attribute fields on existing nodes — no structural
- * reload. If not implemented, the tree falls back to _refetchAllExpanded. */
- refreshAttributes?(ids: string[]): Promise