@anchrd/intel-ui 0.19.0 → 0.21.0
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 +33 -0
- package/package.json +2 -2
- package/src/app/app-tree/app-tree.tsx +45 -5
- package/src/app/header-search/header-search.tsx +2 -1
- package/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
- package/src/app/user-footer/user-footer.tsx +19 -0
- package/src/archive/archive.tsx +2 -1
- package/src/data/intel-data-provider/intel-data-provider.ts +43 -29
- package/src/data/intel-data-provider/intel-data-provider.types.ts +40 -24
- package/src/data/query-client.ts +18 -0
- package/src/data/request-refusal/request-refusal.ts +51 -0
- package/src/document-link/document-link.tsx +1 -1
- package/src/entry-picker/entry-picker.tsx +1 -1
- package/src/flow-runs/flow-runs.tsx +1 -1
- package/src/flows/flows.tsx +11 -3
- package/src/flows/node-icon/node-icon.ts +1 -1
- package/src/flows/node-palette/node-palette.tsx +1 -1
- package/src/flows/node-palette/node-palette.types.ts +1 -1
- package/src/graph-pane/graph-pane.tsx +1 -1
- package/src/i18n/de.json +23 -2
- package/src/i18n/en.json +23 -2
- package/src/i18n/es.json +23 -2
- package/src/main.tsx +3 -2
- package/src/node-editor/node-editor.tsx +15 -2
- package/src/node-graph/graph-notice.tsx +1 -1
- package/src/node-graph/node-graph.ts +1 -1
- package/src/node-graph/node-graph.tsx +1 -1
- package/src/node-table/node-table.tsx +3 -3
- package/src/nodes/nodes.tsx +21 -1
- package/src/resource-menu/resource-menu.tsx +206 -45
- package/src/save-button/save-button.tsx +11 -1
- package/src/tools/tools.tsx +147 -44
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RelationGraph } from "@anchrd/intel-contract";
|
|
1
|
+
import type { RelationGraph } from "@anchrd/intel-contract/flow";
|
|
2
2
|
import type { UseQueryResult } from "@tanstack/react-query";
|
|
3
3
|
import { lazy, Suspense } from "react";
|
|
4
4
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
package/src/i18n/de.json
CHANGED
|
@@ -112,6 +112,14 @@
|
|
|
112
112
|
"node.share": "Freigeben",
|
|
113
113
|
"node.shareAction": "Zugriff geben",
|
|
114
114
|
"node.email": "E-Mail-Adresse",
|
|
115
|
+
"node.emailHint": "Intel kann nicht prüfen, ob hinter dieser Adresse ein Konto steht. Die Freigabe greift, sobald sich jemand damit anmeldet — auch nach einer späteren Registrierung.",
|
|
116
|
+
"node.shareWith": "Wer Zugriff bekommt",
|
|
117
|
+
"node.shareWithEmail": "Eine E-Mail-Adresse",
|
|
118
|
+
"node.shareWithOrganization": "Alle in der Organisation",
|
|
119
|
+
"node.shareOrganizationHint": "Jedes angemeldete Mitglied der Organisation bekommt diesen Zugriff, auch wer später dazukommt.",
|
|
120
|
+
"node.shareLibraryWarning": "Ausführen für die ganze Organisation macht diesen Ordner zu einer Bibliothek: Flows von überall im Baum dürfen dann hineinrufen. Das Zurücknehmen wird abgelehnt, solange einer davon es noch tut.",
|
|
121
|
+
"node.revokeInUse": "Dieser Zugriff lässt sich noch nicht zurücknehmen, weil Flows außerhalb dieses Ordners noch hineinrufen.",
|
|
122
|
+
"node.someUser": "Ein Benutzerkonto",
|
|
115
123
|
"node.verbs": "Was diese Freigabe erlaubt",
|
|
116
124
|
"node.verb.read": "Lesen",
|
|
117
125
|
"node.verb.write": "Schreiben",
|
|
@@ -167,7 +175,9 @@
|
|
|
167
175
|
"tools.connectExpiredHelp": "Die Verbindung zum MCP-Portal der Firma muss innerhalb weniger Minuten abgeschlossen sein, und diese lief ab, während die Freigabe-Seite offen stand. Es ist nichts schiefgegangen und nichts wurde verweigert — starte erneut und bestätige die Server, wenn die Seite erscheint.",
|
|
168
176
|
"tools.connectRetry": "Erneut starten",
|
|
169
177
|
"tools.connectFailed": "Anmeldung am Portal fehlgeschlagen",
|
|
170
|
-
"tools.connectFailedHelp": "Die Anmeldung am MCP-Portal der Firma brach ab, bevor sie beantwortet werden konnte. Das ist ein technischer Fehler, keine Entscheidung über deinen Zugriff —
|
|
178
|
+
"tools.connectFailedHelp": "Die Anmeldung am MCP-Portal der Firma brach ab, bevor sie beantwortet werden konnte. Das ist ein technischer Fehler, keine Entscheidung über deinen Zugriff — starte sie erneut, und sag der Administration Bescheid, wenn es bleibt.",
|
|
179
|
+
"tools.connectUnknown": "Intel weiß nicht, ob du das Portal erreichst",
|
|
180
|
+
"tools.connectUnknownHelp": "In dieser Browsersitzung wurde eine Anmeldung am MCP-Portal der Firma gestartet, die nie mit einer Antwort zurückkam. Deshalb steht hier weder, dass du zugelassen wurdest, noch, dass du abgelehnt wurdest. Starte sie erneut; wenn es dabei bleibt, sag der Administration Bescheid.",
|
|
171
181
|
"tools.origin": "Herkunft",
|
|
172
182
|
"tools.originUnlisted": "Nicht von einem gelisteten Server",
|
|
173
183
|
"tools.originUnlistedHelp": "Die Serverliste des Portals nennt für diese keinen Server. Seine eigenen Verwaltungswerkzeuge liegen hier — das Portal listet sich nicht selbst — und ebenso alles von einem Server, den die Liste nicht erwähnt hat.",
|
|
@@ -283,6 +293,14 @@
|
|
|
283
293
|
"settings.appearance.system": "Dem System folgen",
|
|
284
294
|
"settings.appearance.light": "Hell",
|
|
285
295
|
"settings.appearance.dark": "Dunkel",
|
|
296
|
+
"reindex.title": "Suchindex neu aufbauen",
|
|
297
|
+
"reindex.description": "Liest jeden Knoten noch einmal und ermittelt seine Sucheinträge von Grund auf. Es geht nichts verloren — der Index entsteht aus den Dokumenten selbst —, aber es kostet Zeit und Geld, und danach sieht der Bildschirm genauso aus.",
|
|
298
|
+
"reindex.confirm": "Neu aufbauen",
|
|
299
|
+
"reindex.running": "Wird gesendet …",
|
|
300
|
+
"reindex.cancel": "Abbrechen",
|
|
301
|
+
"reindex.close": "Schließen",
|
|
302
|
+
"reindex.queued": "{count} Knoten sind eingereiht. Der Index zieht im Hintergrund nach; die Suche funktioniert währenddessen weiter.",
|
|
303
|
+
"reindex.failed": "Der Neuaufbau wurde nicht gestartet. Prüfe deine Berechtigung und versuche es noch einmal.",
|
|
286
304
|
"auth.signOut": "Abmelden",
|
|
287
305
|
"auth.signOutFailed": "Das Abmelden ist fehlgeschlagen. Prüfe deine Verbindung und versuche es erneut.",
|
|
288
306
|
"signIn.refusedTitle": "Angemeldet, aber nicht angenommen",
|
|
@@ -294,6 +312,7 @@
|
|
|
294
312
|
"common.save": "Speichern",
|
|
295
313
|
"common.saving": "Wird gespeichert …",
|
|
296
314
|
"common.saved": "Gespeichert",
|
|
315
|
+
"common.saveNothing": "Nichts zu speichern",
|
|
297
316
|
"common.saveDirty": "Ungespeicherte Änderungen speichern",
|
|
298
317
|
"common.unsavedTitle": "Ungespeicherte Änderungen",
|
|
299
318
|
"common.unsavedBody": "Hier gibt es Änderungen, die nicht gespeichert wurden. Wer jetzt geht, verwirft sie.",
|
|
@@ -302,5 +321,7 @@
|
|
|
302
321
|
"common.loading": "Wird geladen …",
|
|
303
322
|
"common.retry": "Erneut versuchen",
|
|
304
323
|
"common.close": "Schließen",
|
|
305
|
-
"common.unavailable": "Intel ist derzeit nicht verfügbar."
|
|
324
|
+
"common.unavailable": "Intel ist derzeit nicht verfügbar.",
|
|
325
|
+
"common.noAccess": "Das ist für dich nicht verfügbar. Es existiert nicht, oder es ist nicht mehr für dich freigegeben.",
|
|
326
|
+
"common.noPermission": "Dir fehlt die Berechtigung, das zu sehen."
|
|
306
327
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -112,6 +112,14 @@
|
|
|
112
112
|
"node.share": "Share",
|
|
113
113
|
"node.shareAction": "Grant access",
|
|
114
114
|
"node.email": "Email address",
|
|
115
|
+
"node.emailHint": "Intel cannot check whether an account exists behind this address. The grant takes effect as soon as somebody signs in with it — including after they register.",
|
|
116
|
+
"node.shareWith": "Who gets access",
|
|
117
|
+
"node.shareWithEmail": "One email address",
|
|
118
|
+
"node.shareWithOrganization": "Everyone in the organization",
|
|
119
|
+
"node.shareOrganizationHint": "Every signed-in member of the organization gets this access, including people who join later.",
|
|
120
|
+
"node.shareLibraryWarning": "Run for the whole organization makes this folder a library: flows anywhere in the tree may then call into it. Taking it back is refused while any of them still does.",
|
|
121
|
+
"node.revokeInUse": "This access cannot be withdrawn yet, because flows outside this folder still call into it.",
|
|
122
|
+
"node.someUser": "A user account",
|
|
115
123
|
"node.verbs": "What this grant allows",
|
|
116
124
|
"node.verb.read": "Read",
|
|
117
125
|
"node.verb.write": "Write",
|
|
@@ -167,7 +175,9 @@
|
|
|
167
175
|
"tools.connectExpiredHelp": "The connection to the company MCP portal has to be completed within a few minutes, and this one ran out while the authorization screen was open. Nothing went wrong and nothing was refused — start it again and confirm the servers when the screen appears.",
|
|
168
176
|
"tools.connectRetry": "Start again",
|
|
169
177
|
"tools.connectFailed": "Portal sign-in failed",
|
|
170
|
-
"tools.connectFailedHelp": "The sign-in to the company MCP portal broke before it could be answered. This is a technical fault, not a decision about your access —
|
|
178
|
+
"tools.connectFailedHelp": "The sign-in to the company MCP portal broke before it could be answered. This is a technical fault, not a decision about your access — start it again, and tell an administrator if it stays.",
|
|
179
|
+
"tools.connectUnknown": "Intel does not know whether you reach the portal",
|
|
180
|
+
"tools.connectUnknownHelp": "A sign-in to the company MCP portal was started in this browser session and never came back with an answer, so nothing here says you were allowed or refused. Start it again; if it keeps ending here, tell an administrator.",
|
|
171
181
|
"tools.origin": "Origin",
|
|
172
182
|
"tools.originUnlisted": "Not from a listed server",
|
|
173
183
|
"tools.originUnlistedHelp": "The portal's server list names no server for these. Its own management tools sit here — the portal does not list itself — and so does anything from a server the list did not mention.",
|
|
@@ -283,6 +293,14 @@
|
|
|
283
293
|
"settings.appearance.system": "Follow the system",
|
|
284
294
|
"settings.appearance.light": "Light",
|
|
285
295
|
"settings.appearance.dark": "Dark",
|
|
296
|
+
"reindex.title": "Rebuild the search index",
|
|
297
|
+
"reindex.description": "Reads every node again and works out its search entries from scratch. Nothing is lost — the index is built from the documents themselves — but it costs time and money, and the screen looks the same afterwards.",
|
|
298
|
+
"reindex.confirm": "Rebuild",
|
|
299
|
+
"reindex.running": "Sending…",
|
|
300
|
+
"reindex.cancel": "Cancel",
|
|
301
|
+
"reindex.close": "Close",
|
|
302
|
+
"reindex.queued": "{count} nodes are queued. The index catches up in the background; searching keeps working meanwhile.",
|
|
303
|
+
"reindex.failed": "The rebuild was not started. Check your access and try again.",
|
|
286
304
|
"auth.signOut": "Sign out",
|
|
287
305
|
"auth.signOutFailed": "Signing out failed. Check your connection and try again.",
|
|
288
306
|
"signIn.refusedTitle": "Signed in, but not accepted",
|
|
@@ -294,6 +312,7 @@
|
|
|
294
312
|
"common.save": "Save",
|
|
295
313
|
"common.saving": "Saving…",
|
|
296
314
|
"common.saved": "Saved",
|
|
315
|
+
"common.saveNothing": "Nothing to save",
|
|
297
316
|
"common.saveDirty": "Save unsaved changes",
|
|
298
317
|
"common.unsavedTitle": "Unsaved changes",
|
|
299
318
|
"common.unsavedBody": "This has changes that have not been saved. Leaving now discards them.",
|
|
@@ -302,5 +321,7 @@
|
|
|
302
321
|
"common.loading": "Loading…",
|
|
303
322
|
"common.retry": "Try again",
|
|
304
323
|
"common.close": "Close",
|
|
305
|
-
"common.unavailable": "Intel is currently unavailable."
|
|
324
|
+
"common.unavailable": "Intel is currently unavailable.",
|
|
325
|
+
"common.noAccess": "This is not available to you. It may not exist, or it may no longer be shared with you.",
|
|
326
|
+
"common.noPermission": "You do not have permission to see this."
|
|
306
327
|
}
|
package/src/i18n/es.json
CHANGED
|
@@ -112,6 +112,14 @@
|
|
|
112
112
|
"node.share": "Compartir",
|
|
113
113
|
"node.shareAction": "Conceder acceso",
|
|
114
114
|
"node.email": "Dirección de correo",
|
|
115
|
+
"node.emailHint": "Intel no puede comprobar si existe una cuenta detrás de esta dirección. El permiso surte efecto en cuanto alguien inicie sesión con ella, incluso si se registra más tarde.",
|
|
116
|
+
"node.shareWith": "Quién obtiene acceso",
|
|
117
|
+
"node.shareWithEmail": "Una dirección de correo",
|
|
118
|
+
"node.shareWithOrganization": "Todos en la organización",
|
|
119
|
+
"node.shareOrganizationHint": "Todos los miembros de la organización que hayan iniciado sesión obtienen este acceso, incluidos quienes se incorporen más tarde.",
|
|
120
|
+
"node.shareLibraryWarning": "Ejecutar para toda la organización convierte esta carpeta en una biblioteca: los flujos de cualquier parte del árbol podrán invocarla. Retirarlo se rechaza mientras alguno siga haciéndolo.",
|
|
121
|
+
"node.revokeInUse": "Este acceso todavía no se puede retirar, porque hay flujos fuera de esta carpeta que siguen invocándola.",
|
|
122
|
+
"node.someUser": "Una cuenta de usuario",
|
|
115
123
|
"node.verbs": "Qué permite este acceso",
|
|
116
124
|
"node.verb.read": "Leer",
|
|
117
125
|
"node.verb.write": "Escribir",
|
|
@@ -167,7 +175,9 @@
|
|
|
167
175
|
"tools.connectExpiredHelp": "La conexión con el portal MCP de la empresa tiene que completarse en unos minutos, y esta se ha agotado mientras la pantalla de autorización estaba abierta. No ha fallado nada ni se ha denegado nada — vuelve a empezar y confirma los servidores cuando aparezca la pantalla.",
|
|
168
176
|
"tools.connectRetry": "Empezar de nuevo",
|
|
169
177
|
"tools.connectFailed": "El inicio de sesión en el portal ha fallado",
|
|
170
|
-
"tools.connectFailedHelp": "El inicio de sesión en el portal MCP de la empresa se ha interrumpido antes de poder responderse. Es un fallo técnico, no una decisión sobre tu acceso —
|
|
178
|
+
"tools.connectFailedHelp": "El inicio de sesión en el portal MCP de la empresa se ha interrumpido antes de poder responderse. Es un fallo técnico, no una decisión sobre tu acceso — vuelve a empezar y avisa a la administración si persiste.",
|
|
179
|
+
"tools.connectUnknown": "Intel no sabe si llegas al portal",
|
|
180
|
+
"tools.connectUnknownHelp": "En esta sesión del navegador se inició un acceso al portal MCP de la empresa que nunca volvió con una respuesta, así que aquí no dice ni que se te haya permitido ni que se te haya denegado. Vuelve a empezar; si sigue acabando aquí, avisa a la administración.",
|
|
171
181
|
"tools.origin": "Origen",
|
|
172
182
|
"tools.originUnlisted": "No procede de un servidor listado",
|
|
173
183
|
"tools.originUnlistedHelp": "La lista de servidores del portal no nombra ningún servidor para estas. Aquí están sus propias herramientas de gestión — el portal no se lista a sí mismo — y también todo lo que venga de un servidor que la lista no mencionó.",
|
|
@@ -283,6 +293,14 @@
|
|
|
283
293
|
"settings.appearance.system": "Seguir al sistema",
|
|
284
294
|
"settings.appearance.light": "Claro",
|
|
285
295
|
"settings.appearance.dark": "Oscuro",
|
|
296
|
+
"reindex.title": "Reconstruir el índice de búsqueda",
|
|
297
|
+
"reindex.description": "Vuelve a leer cada nodo y calcula sus entradas de búsqueda desde cero. No se pierde nada — el índice se construye a partir de los propios documentos —, pero cuesta tiempo y dinero, y después la pantalla se ve igual.",
|
|
298
|
+
"reindex.confirm": "Reconstruir",
|
|
299
|
+
"reindex.running": "Enviando…",
|
|
300
|
+
"reindex.cancel": "Cancelar",
|
|
301
|
+
"reindex.close": "Cerrar",
|
|
302
|
+
"reindex.queued": "{count} nodos están en cola. El índice se pone al día en segundo plano; mientras tanto la búsqueda sigue funcionando.",
|
|
303
|
+
"reindex.failed": "No se inició la reconstrucción. Comprueba tu acceso e inténtalo de nuevo.",
|
|
286
304
|
"auth.signOut": "Cerrar sesión",
|
|
287
305
|
"auth.signOutFailed": "El cierre de sesión ha fallado. Comprueba tu conexión e inténtalo de nuevo.",
|
|
288
306
|
"signIn.refusedTitle": "Sesión iniciada, pero no aceptada",
|
|
@@ -294,6 +312,7 @@
|
|
|
294
312
|
"common.save": "Guardar",
|
|
295
313
|
"common.saving": "Guardando…",
|
|
296
314
|
"common.saved": "Guardado",
|
|
315
|
+
"common.saveNothing": "Nada que guardar",
|
|
297
316
|
"common.saveDirty": "Guardar los cambios sin guardar",
|
|
298
317
|
"common.unsavedTitle": "Cambios sin guardar",
|
|
299
318
|
"common.unsavedBody": "Esto tiene cambios que no se han guardado. Si sales ahora, se descartan.",
|
|
@@ -302,5 +321,7 @@
|
|
|
302
321
|
"common.loading": "Cargando…",
|
|
303
322
|
"common.retry": "Intentarlo de nuevo",
|
|
304
323
|
"common.close": "Cerrar",
|
|
305
|
-
"common.unavailable": "Intel no está disponible en este momento."
|
|
324
|
+
"common.unavailable": "Intel no está disponible en este momento.",
|
|
325
|
+
"common.noAccess": "Esto no está disponible para ti. Puede que no exista o que ya no esté compartido contigo.",
|
|
326
|
+
"common.noPermission": "No tienes permiso para ver esto."
|
|
306
327
|
}
|
package/src/main.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
2
2
|
import { RouterProvider } from "@tanstack/react-router";
|
|
3
3
|
import { StrictMode } from "react";
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createIntelDataProvider,
|
|
7
7
|
loginPath,
|
|
8
8
|
} from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
9
|
+
import { createIntelQueryClient } from "@/data/query-client.ts";
|
|
9
10
|
import { createBrowserSignIn, createSignInGuard } from "@/data/sign-in/sign-in.ts";
|
|
10
11
|
import { I18nProvider } from "@/i18n/i18n-context.tsx";
|
|
11
12
|
import { createLanguages } from "@/i18n/i18n-languages/i18n-languages.ts";
|
|
@@ -32,7 +33,7 @@ if (!root) throw new Error("Missing #root element");
|
|
|
32
33
|
const languages = createLanguages({ store: window.localStorage });
|
|
33
34
|
const signIn = createBrowserSignIn(loginPath);
|
|
34
35
|
const mounted = createRoot(root);
|
|
35
|
-
const queryClient =
|
|
36
|
+
const queryClient = createIntelQueryClient();
|
|
36
37
|
|
|
37
38
|
// The shell is painted from a function because a refused sign-in has to replace it, and that
|
|
38
39
|
// refusal arrives from inside a fetch rather than from React.
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
DocumentLinkInlineType,
|
|
5
5
|
type Node,
|
|
6
6
|
type NodeDocument,
|
|
7
|
-
} from "@anchrd/intel-contract";
|
|
7
|
+
} from "@anchrd/intel-contract/node";
|
|
8
8
|
import { filterSuggestionItems } from "@blocknote/core";
|
|
9
9
|
import { SuggestionMenuController, useCreateBlockNote } from "@blocknote/react";
|
|
10
10
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
@@ -120,6 +120,11 @@ export function NodeEditor({
|
|
|
120
120
|
mediaType: BlockNoteMediaType,
|
|
121
121
|
idempotencyKey: crypto.randomUUID(),
|
|
122
122
|
});
|
|
123
|
+
// ⚠️ An answer that carries no version did not save anything (#432). Letting it through would
|
|
124
|
+
// move `savedSnapshot` forward, drop the unsaved warning and leave the reader free to close
|
|
125
|
+
// the tab — over a document the server still reports as empty. It belongs on the error path,
|
|
126
|
+
// which is the one place the screen already says that the writing failed.
|
|
127
|
+
if (!node.version) throw new Error("save_without_version");
|
|
123
128
|
// ⚠️ The snapshot of what was SENT, not of what stands in the editor when the answer comes
|
|
124
129
|
// back. Saving crosses the network, and typing during it is normal — taking the state at
|
|
125
130
|
// arrival would call that typing saved and lose it at the next navigation.
|
|
@@ -162,7 +167,15 @@ export function NodeEditor({
|
|
|
162
167
|
because this is where `dirty` lives — moving the state up to the screen would mean the
|
|
163
168
|
screen tracking every keystroke in an editor it does not own. */}
|
|
164
169
|
<ActionSlot name="title-actions">
|
|
165
|
-
|
|
170
|
+
{/* ⚠️ `stored` comes from the document the screen holds, not from a flag this component
|
|
171
|
+
sets on success: it turns true when the answer that carried a version has reached the
|
|
172
|
+
query cache, and never a moment earlier (#432). */}
|
|
173
|
+
<SaveButton
|
|
174
|
+
dirty={dirty}
|
|
175
|
+
saving={save.isPending}
|
|
176
|
+
stored={document.version !== null}
|
|
177
|
+
onSave={() => save.mutate()}
|
|
178
|
+
/>
|
|
166
179
|
</ActionSlot>
|
|
167
180
|
<UnsavedChangesGuard dirty={dirty} />
|
|
168
181
|
{save.isError && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RelationGraph } from "@anchrd/intel-contract";
|
|
1
|
+
import type { RelationGraph } from "@anchrd/intel-contract/flow";
|
|
2
2
|
import { ControlsContainer, SigmaContainer, useCamera, useRegisterEvents } from "@react-sigma/core";
|
|
3
3
|
import { LocateFixed, Minus, Plus } from "lucide-react";
|
|
4
4
|
import { useEffect, useMemo } from "react";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Node } from "@anchrd/intel-contract";
|
|
1
|
+
import type { Node } from "@anchrd/intel-contract/node";
|
|
2
2
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { Download } from "lucide-react";
|
|
4
4
|
import { ActionSlot } from "@/app/action-slot/action-slot.tsx";
|
|
@@ -9,8 +9,8 @@ import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
|
9
9
|
* A table as a grid (#40).
|
|
10
10
|
*
|
|
11
11
|
* Reading is the whole of the first cut: there is no cell editor, and rows arrive through
|
|
12
|
-
* `
|
|
13
|
-
* person retyping anything. Editing in the grid is anchrd/intel#165.
|
|
12
|
+
* `node_table_row_create` — the point of the kind is that a flow or an MCP client fills it
|
|
13
|
+
* without a person retyping anything. Editing in the grid is anchrd/intel#165.
|
|
14
14
|
*
|
|
15
15
|
* ⚠️ The rows are the server's answer, parsed by the server's one CSV reader. A second reader here
|
|
16
16
|
* would eventually disagree with it about a quoted comma, and the grid would then show something
|
package/src/nodes/nodes.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Node } from "@anchrd/intel-contract";
|
|
1
|
+
import type { Node } from "@anchrd/intel-contract/node";
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
4
4
|
import { Download, History, Paperclip } from "lucide-react";
|
|
@@ -6,6 +6,7 @@ import { lazy, Suspense, useState } from "react";
|
|
|
6
6
|
import { ActionSlot } from "@/app/action-slot/action-slot.tsx";
|
|
7
7
|
import { ViewToggle } from "@/app/view-toggle/view-toggle.tsx";
|
|
8
8
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
9
|
+
import { refusalOf } from "@/data/request-refusal/request-refusal.ts";
|
|
9
10
|
import { FolderContents } from "@/folder-contents/folder-contents.tsx";
|
|
10
11
|
import { GraphPane } from "@/graph-pane/graph-pane.tsx";
|
|
11
12
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
@@ -39,6 +40,10 @@ export function Nodes() {
|
|
|
39
40
|
enabled: Boolean(selectedId),
|
|
40
41
|
});
|
|
41
42
|
const selected = document.data?.node ?? null;
|
|
43
|
+
// ⚠️ Off the ERROR, not off the absence of data: a query that has not answered is not a query that
|
|
44
|
+
// answered "no" (`packages/ui/CLAUDE.md`). While the request is in flight there is no refusal to
|
|
45
|
+
// report, and the loading line below stays.
|
|
46
|
+
const refusal = refusalOf(document.error);
|
|
42
47
|
// A folder shows its contents as a graph, and so does the root of the tree (#19). A document has
|
|
43
48
|
// nothing to draw — which is why the switch is not offered on one rather than offered and empty.
|
|
44
49
|
const graphable = selectedId === null || selected?.kind === "folder";
|
|
@@ -80,6 +85,21 @@ export function Nodes() {
|
|
|
80
85
|
</div>
|
|
81
86
|
) : document.isPending ? (
|
|
82
87
|
<p className="p-6 text-sm text-muted-foreground">{i18n.t("common.loading")}</p>
|
|
88
|
+
) : refusal ? (
|
|
89
|
+
/* ⚠️ A refusal is not a failure, and the difference is the whole of #430. Somebody whose
|
|
90
|
+
share was revoked was left in front of "Loading…" — the worst answer of all, because it
|
|
91
|
+
says the program is still working. They get a sentence and no retry button: the answer
|
|
92
|
+
was final, and a button that changes nothing is an invitation to keep waiting.
|
|
93
|
+
|
|
94
|
+
⚠️ One sentence for `404`, whichever of its two reasons applies. Intel answers the same
|
|
95
|
+
status for "no such node" and "not for you" on purpose — `requireVisible` in
|
|
96
|
+
`packages/api/src/nodes/nodes.ts` — and a screen that told them apart would undo that
|
|
97
|
+
from the other side. */
|
|
98
|
+
<div role="status" className="grid flex-1 place-items-center p-8 text-center text-sm">
|
|
99
|
+
<p className="max-w-sm text-muted-foreground">
|
|
100
|
+
{i18n.t(refusal === "no-permission" ? "common.noPermission" : "common.noAccess")}
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
83
103
|
) : document.isError || !selected ? (
|
|
84
104
|
<div role="alert" className="grid flex-1 place-items-center p-8 text-center text-sm">
|
|
85
105
|
<div className="space-y-3">
|