@anchrd/intel-ui 0.7.2 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "typecheck": "tsc --noEmit"
29
29
  },
30
30
  "dependencies": {
31
- "@anchrd/intel-contract": "^0.3.0",
31
+ "@anchrd/intel-contract": "^0.4.0",
32
32
  "@blocknote/core": "^0.52.1",
33
33
  "@blocknote/react": "^0.52.1",
34
34
  "@blocknote/shadcn": "^0.52.1",
@@ -119,6 +119,14 @@ export function AppTree() {
119
119
  // ⚠️ The whole point of #11: one query per level, and a level only exists while its folder is
120
120
  // open. A recursive load would put an N+1 on every page of the app, because the tree is now on
121
121
  // every page.
122
+ // Ein aufgeklappter Flow zeigt, was er ruft — mit demselben Pfeil-Versprechen eine Ebene tiefer:
123
+ // ein gerufener Flow, der selbst ruft, lässt sich weiter aufklappen (#59).
124
+ async function flowCallRows(flowId: string): Promise<TreeEntry[]> {
125
+ const calls = await data.listFlowCalls(flowId);
126
+ const openable = new Set(calls.withCalls);
127
+ return calls.items.map((flow) => flowEntry(flow, openable.has(flow.id)));
128
+ }
129
+
122
130
  const parents: Level[] = [{ id: null, type: "folder" }, ...expanded];
123
131
  const levels = useQueries({
124
132
  queries: parents.map((parent) => ({
@@ -128,7 +136,7 @@ export function AppTree() {
128
136
  ? // The same row a folder's level builds for a flow, from the same function: what hangs
129
137
  // under an expanded flow differs in where the flows come from, never in what a flow row
130
138
  // is (#30).
131
- (await data.listFlowCalls(parent.id)).items.map(flowEntry)
139
+ await flowCallRows(parent.id)
132
140
  : await data.listTreeChildren(parent.id),
133
141
  })),
134
142
  });
@@ -338,17 +346,14 @@ export function AppTree() {
338
346
  // now is that a row already on the path from the root is not rendered again.
339
347
  const entries = level.data?.filter((entry) => !ancestors.has(entry.id)) ?? [];
340
348
  if (entries.length === 0) {
341
- // ⚠️ An open folder with nothing in it says nothing (#24): the missing row already is the
342
- // answer, and the sentence used to repeat itself once per opened folder. The other two stay,
343
- // and deliberately: the empty root is the whole of the navigation, so a blank sidebar on a
344
- // fresh installation would leave a new user without any way in; and "this flow calls no other
345
- // flow" is a different statement from "empty" — it is an answer, not filler.
346
- const message =
347
- parent.type === "flow"
348
- ? i18n.t("tree.noCalls")
349
- : parent.id === null
350
- ? i18n.t("tree.empty")
351
- : null;
349
+ // ⚠️ Nothing that is empty says so any more (#59). "This flow calls no other flow" used to
350
+ // stand here as an answer rather than filler but a row that cannot be opened is the answer,
351
+ // and now no such row can be opened: the chevron only appears where there is something under
352
+ // it. The sentence would only ever be read by somebody who got here another way.
353
+ //
354
+ // `tree.empty` at the ROOT stays, and for a different reason that still holds: a completely
355
+ // blank sidebar on a fresh installation leaves a new user without any way in.
356
+ const message = parent.type === "folder" && parent.id === null ? i18n.t("tree.empty") : null;
352
357
  // The folder keeps its list, empty. The structure is what carries "there is nothing here" to
353
358
  // a screen reader — "list, 0 items" — which is the same answer the missing rows give a reader
354
359
  // who can see them, and neither of them is a sentence.
@@ -374,7 +379,10 @@ export function AppTree() {
374
379
  // from `parent_id`. A flow reused by three callers therefore shows up under all three, which is
375
380
  // the answer to "what do I run, and how" (ADR-0004 §3).
376
381
  const level: Level & { id: string } = { id: entry.id, type: isFolder ? "folder" : "flow" };
377
- const expandable = isFolder || entry.type === "flow";
382
+ // ⚠️ Nicht mehr „ist von einer Art, die sich aufklappen lässt", sondern „hat etwas zum
383
+ // Aufklappen, das dieser Leser sehen darf" (#59). Der Unterschied ist ein Pfeil, der hält, was
384
+ // er verspricht — vorher stand er an jedem Ordner und jedem Flow, auch an leeren.
385
+ const expandable = entry.openable;
378
386
  const isOpen =
379
387
  expandable && expanded.some((open) => open.id === entry.id && open.type === level.type);
380
388
  const Icon = isOpen && isFolder ? FolderOpen : icons[entry.kind];
@@ -80,8 +80,12 @@ export function loginPath(returnTo?: string): string {
80
80
  * copies of this would keep returning something plausible and slowly stop agreeing on what a flow
81
81
  * row is (#30).
82
82
  */
83
- export function flowEntry(flow: Flow): TreeEntry {
84
- return { type: "flow", id: flow.id, title: flow.title, kind: "flow", flow };
83
+ // ⚠️ `openable` ist hier voreingestellt falsch — genauer: unbekannt. Wer die Ebene kennt, setzt es
84
+ // (`listTreeChildren`); wer nur eine einzelne Zeile baut, weiß es nicht und behauptet deshalb das
85
+ // Zurückhaltendere. Ein Pfeil, der zu Unrecht fehlt, kostet einen Klick auf die Zeile; einer, der
86
+ // zu Unrecht steht, kostet Vertrauen (#59).
87
+ export function flowEntry(flow: Flow, openable = false): TreeEntry {
88
+ return { type: "flow", id: flow.id, title: flow.title, kind: "flow", openable, flow };
85
89
  }
86
90
 
87
91
  export function createIntelDataProvider(
@@ -181,15 +185,20 @@ export function createIntelDataProvider(
181
185
  listKnowledge({ parentId }),
182
186
  listFlows({ parentId }),
183
187
  ]);
188
+ // ⚠️ Beide Seiten sagen, welche ihrer Zeilen etwas zum Aufklappen haben (#59) — für DIESEN
189
+ // Leser. Der Baum weiß es damit beim Zeichnen, ohne die Ebene darunter vorzuladen: Das wäre
190
+ // ein Ladevorgang pro Zeile statt pro Ordner.
191
+ const openable = new Set([...knowledge.withChildren, ...flows.withCalls]);
184
192
  const entries: TreeEntry[] = [
185
193
  ...knowledge.items.map((node) => ({
186
194
  type: "knowledge" as const,
187
195
  id: node.id,
188
196
  title: node.title,
189
197
  kind: node.kind,
198
+ openable: openable.has(node.id),
190
199
  node,
191
200
  })),
192
- ...flows.items.map(flowEntry),
201
+ ...flows.items.map((flow) => flowEntry(flow, openable.has(flow.id))),
193
202
  ];
194
203
  // Folders first, then everything else by title: a document and a flow sit side by side, and
195
204
  // the icon is what tells them apart.
@@ -51,9 +51,19 @@ import type {
51
51
 
52
52
  // One row of the shared tree. Knowledge and Flows share the folder, not their nature (ADR-0004), so
53
53
  // this is a union that keeps each side's record whole — never a merged "node" that is a bit of both.
54
+ // ⚠️ `openable` heißt „hat etwas zum Aufklappen, das DIESER Leser sehen darf" (#59) — nicht „ist
55
+ // von einer Art, die sich aufklappen lässt". Der Unterschied ist der ganze Sinn: Ein Pfeil an einer
56
+ // Zeile, unter der nichts liegt, verspricht Inhalt, den das Aufklappen nicht liefert.
54
57
  export type TreeEntry =
55
- | { type: "knowledge"; id: string; title: string; kind: KnowledgeNodeKind; node: KnowledgeNode }
56
- | { type: "flow"; id: string; title: string; kind: "flow"; flow: Flow };
58
+ | {
59
+ type: "knowledge";
60
+ id: string;
61
+ title: string;
62
+ kind: KnowledgeNodeKind;
63
+ openable: boolean;
64
+ node: KnowledgeNode;
65
+ }
66
+ | { type: "flow"; id: string; title: string; kind: "flow"; openable: boolean; flow: Flow };
57
67
 
58
68
  export interface IntelDataProvider {
59
69
  getSession(): Promise<SessionUser>;
package/src/i18n/en.json CHANGED
@@ -32,7 +32,6 @@
32
32
  "tree.collapse": "Hide what is inside {title}",
33
33
  "tree.expandCalls": "Show the flows {title} calls",
34
34
  "tree.collapseCalls": "Hide the flows {title} calls",
35
- "tree.noCalls": "This flow calls no other flow.",
36
35
  "tree.add": "Add to {title}",
37
36
  "tree.addRoot": "Add at the top level",
38
37
  "tree.new.folder": "New folder",
@@ -61,6 +61,8 @@ function entryOf(target: ResourceTarget): TreeEntry {
61
61
  id: target.node.id,
62
62
  title: target.node.title,
63
63
  kind: target.node.kind,
64
+ // Ein Menü verschiebt eine Zeile; ob sie sich aufklappen lässt, spielt dabei keine Rolle.
65
+ openable: false,
64
66
  node: target.node,
65
67
  };
66
68
  }