@manta-eu/react 0.2.1 → 0.4.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.
Files changed (187) hide show
  1. package/dist/components/datastore-management/tree.d.ts +1 -1
  2. package/dist/components/datastore-management/tree.d.ts.map +1 -1
  3. package/dist/components/datastore-management/tree.js +5 -5
  4. package/dist/components/datastore-management/tree.js.map +1 -1
  5. package/dist/components/explain-fact/ExplainFact.js +3 -3
  6. package/dist/components/explain-fact/ExplainFact.js.map +1 -1
  7. package/dist/components/planning/plan-detail-utils.d.ts.map +1 -1
  8. package/dist/components/planning/plan-detail-utils.js +4 -0
  9. package/dist/components/planning/plan-detail-utils.js.map +1 -1
  10. package/dist/components/tool-invocation-card/ListRulesResultDisplay.d.ts +2 -2
  11. package/dist/components/tool-invocation-card/ListRulesResultDisplay.d.ts.map +1 -1
  12. package/dist/components/tool-invocation-card/ToolOutputRenderer.d.ts.map +1 -1
  13. package/dist/components/tool-invocation-card/ToolOutputRenderer.js +7 -4
  14. package/dist/components/tool-invocation-card/ToolOutputRenderer.js.map +1 -1
  15. package/dist/components/tool-invocation-card/types.d.ts +5 -5
  16. package/dist/components/tool-invocation-card/types.d.ts.map +1 -1
  17. package/dist/components/tool-invocation-card/types.js +5 -5
  18. package/dist/components/tool-invocation-card/types.js.map +1 -1
  19. package/dist/explain-fact/rule-presentation.js +2 -2
  20. package/dist/explain-fact/rule-presentation.js.map +1 -1
  21. package/dist/explain-fact/tree-model.js +2 -2
  22. package/dist/explain-fact/tree-model.js.map +1 -1
  23. package/dist/graph/EntityCard.d.ts.map +1 -1
  24. package/dist/graph/EntityCard.js +2 -1
  25. package/dist/graph/EntityCard.js.map +1 -1
  26. package/dist/graph/EntityViewWithLinks.d.ts +7 -0
  27. package/dist/graph/EntityViewWithLinks.d.ts.map +1 -0
  28. package/dist/graph/EntityViewWithLinks.js +28 -0
  29. package/dist/graph/EntityViewWithLinks.js.map +1 -0
  30. package/dist/graph/card-links.d.ts +19 -0
  31. package/dist/graph/card-links.d.ts.map +1 -0
  32. package/dist/graph/card-links.js +23 -0
  33. package/dist/graph/card-links.js.map +1 -0
  34. package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
  35. package/dist/graph/nodes/BrandAssetAnalysisView.js +1 -1
  36. package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
  37. package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
  38. package/dist/graph/nodes/BrandFontSuggestionView.js +1 -1
  39. package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
  40. package/dist/graph/nodes/EntityCardShell.d.ts +2 -0
  41. package/dist/graph/nodes/EntityCardShell.d.ts.map +1 -1
  42. package/dist/graph/nodes/EntityCardShell.js +13 -3
  43. package/dist/graph/nodes/EntityCardShell.js.map +1 -1
  44. package/dist/graph/nodes/NodePropertyTable.d.ts +2 -1
  45. package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
  46. package/dist/graph/nodes/NodePropertyTable.js +10 -1
  47. package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
  48. package/dist/graph/nodes/node-property-display.d.ts +0 -1
  49. package/dist/graph/nodes/node-property-display.d.ts.map +1 -1
  50. package/dist/graph/nodes/node-property-display.js +2 -6
  51. package/dist/graph/nodes/node-property-display.js.map +1 -1
  52. package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -1
  53. package/dist/graph/special-nodes/AttributeList.js +3 -2
  54. package/dist/graph/special-nodes/AttributeList.js.map +1 -1
  55. package/dist/graph/special-nodes/RelatedSection.d.ts +6 -0
  56. package/dist/graph/special-nodes/RelatedSection.d.ts.map +1 -0
  57. package/dist/graph/special-nodes/RelatedSection.js +16 -0
  58. package/dist/graph/special-nodes/RelatedSection.js.map +1 -0
  59. package/dist/graph/special-nodes/card-facts.d.ts +2 -0
  60. package/dist/graph/special-nodes/card-facts.d.ts.map +1 -1
  61. package/dist/graph/special-nodes/card-facts.js +11 -0
  62. package/dist/graph/special-nodes/card-facts.js.map +1 -1
  63. package/dist/graph/types.d.ts +2 -2
  64. package/dist/graph/types.d.ts.map +1 -1
  65. package/dist/hooks/use-approve-anyway.d.ts.map +1 -1
  66. package/dist/hooks/use-approve-anyway.js +8 -8
  67. package/dist/hooks/use-approve-anyway.js.map +1 -1
  68. package/dist/hooks/use-connection-problems.d.ts.map +1 -1
  69. package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
  70. package/dist/ldo-ui.d.ts +1 -0
  71. package/dist/ldo-ui.d.ts.map +1 -1
  72. package/dist/ldo-ui.js +1 -0
  73. package/dist/ldo-ui.js.map +1 -1
  74. package/dist/lib/find-latest-staged-rule.js +1 -1
  75. package/dist/lib/find-latest-staged-rule.js.map +1 -1
  76. package/dist/lib/tool-icons.js +6 -6
  77. package/dist/lib/tool-icons.js.map +1 -1
  78. package/dist/lib/tool-names.d.ts.map +1 -1
  79. package/dist/lib/tool-names.js +0 -1
  80. package/dist/lib/tool-names.js.map +1 -1
  81. package/dist/ontology/skolem.js +2 -2
  82. package/dist/ontology/skolem.js.map +1 -1
  83. package/dist/providers/KnowledgeGraphStateProvider.js +1 -1
  84. package/dist/providers/KnowledgeGraphStateProvider.js.map +1 -1
  85. package/dist/routes/data-explorer/class-properties.js +2 -2
  86. package/dist/routes/data-explorer/class-properties.js.map +1 -1
  87. package/dist/routes/data-explorer/classes.js +2 -2
  88. package/dist/routes/data-explorer/classes.js.map +1 -1
  89. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.d.ts.map +1 -1
  90. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js +7 -21
  91. package/dist/routes/data-explorer/components/record-detail/ActionFormDialog.js.map +1 -1
  92. package/dist/routes/data-explorer/components/record-detail/ChatPreview.d.ts.map +1 -1
  93. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
  94. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
  95. package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts +2 -2
  96. package/dist/routes/data-explorer/components/record-detail/action-properties.d.ts.map +1 -1
  97. package/dist/routes/data-explorer/components/record-detail/action-properties.js +3 -3
  98. package/dist/routes/data-explorer/components/record-detail/action-properties.js.map +1 -1
  99. package/dist/routes/data-explorer/recents.js +1 -1
  100. package/dist/routes/data-explorer/recents.js.map +1 -1
  101. package/dist/routes/data-explorer/shapes.js +2 -2
  102. package/dist/routes/data-explorer/shapes.js.map +1 -1
  103. package/dist/routes/integrations/catalog-shape.d.ts +1 -0
  104. package/dist/routes/integrations/catalog-shape.d.ts.map +1 -1
  105. package/dist/routes/integrations/catalog-shape.js.map +1 -1
  106. package/dist/routes/integrations/components/connect-forms/CanvaConnectForm.d.ts +3 -0
  107. package/dist/routes/integrations/components/connect-forms/CanvaConnectForm.d.ts.map +1 -0
  108. package/dist/routes/integrations/components/connect-forms/CanvaConnectForm.js +12 -0
  109. package/dist/routes/integrations/components/connect-forms/CanvaConnectForm.js.map +1 -0
  110. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts +7 -0
  111. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.d.ts.map +1 -0
  112. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js +76 -0
  113. package/dist/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.js.map +1 -0
  114. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts +7 -0
  115. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.d.ts.map +1 -1
  116. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js +8 -7
  117. package/dist/routes/integrations/components/connect-forms/OAuthSetupConnectForm.js.map +1 -1
  118. package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts +3 -0
  119. package/dist/routes/integrations/components/connect-forms/inferal-locations.d.ts.map +1 -0
  120. package/dist/routes/integrations/components/connect-forms/inferal-locations.js +13 -0
  121. package/dist/routes/integrations/components/connect-forms/inferal-locations.js.map +1 -0
  122. package/dist/routes/integrations/connect-forms.d.ts +3 -0
  123. package/dist/routes/integrations/connect-forms.d.ts.map +1 -1
  124. package/dist/routes/integrations/connect-forms.js +3 -0
  125. package/dist/routes/integrations/connect-forms.js.map +1 -1
  126. package/dist/routes/integrations/connection-view.d.ts.map +1 -1
  127. package/dist/routes/integrations/connection-view.js +2 -0
  128. package/dist/routes/integrations/connection-view.js.map +1 -1
  129. package/dist/routes/integrations/types.d.ts +2 -1
  130. package/dist/routes/integrations/types.d.ts.map +1 -1
  131. package/dist/routes/integrations/types.js +1 -0
  132. package/dist/routes/integrations/types.js.map +1 -1
  133. package/dist/routes/rules/hooks/useRuleFactsPreview.js +4 -4
  134. package/dist/routes/rules/hooks/useRuleFactsPreview.js.map +1 -1
  135. package/dist/shacl/form-appearance.d.ts +1 -1
  136. package/dist/shacl/form-appearance.d.ts.map +1 -1
  137. package/dist/shacl/form-appearance.js +1 -1
  138. package/dist/shacl/form-appearance.js.map +1 -1
  139. package/dist/shared/graph-derived-queries.js +2 -2
  140. package/dist/shared/graph-derived-queries.js.map +1 -1
  141. package/package.json +7 -7
  142. package/src/components/datastore-management/tree.ts +5 -5
  143. package/src/components/explain-fact/ExplainFact.tsx +3 -3
  144. package/src/components/planning/plan-detail-utils.tsx +4 -0
  145. package/src/components/tool-invocation-card/ListRulesResultDisplay.tsx +2 -2
  146. package/src/components/tool-invocation-card/ToolOutputRenderer.tsx +10 -8
  147. package/src/components/tool-invocation-card/types.ts +8 -8
  148. package/src/explain-fact/rule-presentation.ts +2 -2
  149. package/src/explain-fact/tree-model.ts +2 -2
  150. package/src/graph/EntityCard.tsx +2 -1
  151. package/src/graph/EntityViewWithLinks.tsx +43 -0
  152. package/src/graph/card-links.tsx +43 -0
  153. package/src/graph/nodes/BrandAssetAnalysisView.tsx +1 -0
  154. package/src/graph/nodes/BrandFontSuggestionView.tsx +1 -0
  155. package/src/graph/nodes/EntityCardShell.tsx +37 -3
  156. package/src/graph/nodes/NodePropertyTable.tsx +16 -2
  157. package/src/graph/nodes/node-property-display.ts +2 -7
  158. package/src/graph/special-nodes/AttributeList.tsx +4 -2
  159. package/src/graph/special-nodes/RelatedSection.tsx +29 -0
  160. package/src/graph/special-nodes/card-facts.ts +13 -0
  161. package/src/graph/types.ts +1 -1
  162. package/src/hooks/use-approve-anyway.ts +8 -11
  163. package/src/ldo-ui.ts +1 -0
  164. package/src/lib/find-latest-staged-rule.ts +1 -1
  165. package/src/lib/tool-icons.ts +6 -6
  166. package/src/lib/tool-names.ts +0 -1
  167. package/src/ontology/skolem.ts +2 -2
  168. package/src/providers/KnowledgeGraphStateProvider.tsx +1 -1
  169. package/src/routes/data-explorer/class-properties.ts +2 -2
  170. package/src/routes/data-explorer/classes.ts +2 -2
  171. package/src/routes/data-explorer/components/record-detail/ActionFormDialog.tsx +7 -29
  172. package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +6 -1
  173. package/src/routes/data-explorer/components/record-detail/action-properties.ts +3 -3
  174. package/src/routes/data-explorer/recents.ts +1 -1
  175. package/src/routes/data-explorer/shapes.ts +2 -2
  176. package/src/routes/datastores/flatten.ts +1 -1
  177. package/src/routes/integrations/catalog-shape.ts +2 -0
  178. package/src/routes/integrations/components/connect-forms/CanvaConnectForm.tsx +26 -0
  179. package/src/routes/integrations/components/connect-forms/MicrosoftAdsConnectForm.tsx +140 -0
  180. package/src/routes/integrations/components/connect-forms/OAuthSetupConnectForm.tsx +27 -5
  181. package/src/routes/integrations/components/connect-forms/inferal-locations.ts +13 -0
  182. package/src/routes/integrations/connect-forms.ts +3 -0
  183. package/src/routes/integrations/connection-view.ts +2 -0
  184. package/src/routes/integrations/types.ts +2 -0
  185. package/src/routes/rules/hooks/useRuleFactsPreview.ts +4 -4
  186. package/src/shacl/form-appearance.ts +1 -1
  187. package/src/shared/graph-derived-queries.ts +2 -2
@@ -0,0 +1,43 @@
1
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
2
+ import { createContext, type ReactNode, useContext } from "react";
3
+ import type { NodeRelation } from "./types";
4
+
5
+ // The named records a card links to, for the card to list at the top of its frame.
6
+ export type CardLinks = { relations: NodeRelation[] };
7
+
8
+ // A surface such as chat hands the card the records it links to; the canvas sets none.
9
+ const CardLinksContext = createContext<CardLinks | null>(null);
10
+
11
+ const noRecords: ReadonlySet<IRI> = new Set();
12
+
13
+ // The records the card already shows above its content, so a property table leaves them out.
14
+ const LinksShownAboveContext = createContext(noRecords);
15
+
16
+ export type CardLinksProviderProps = { links: CardLinks | null; children: ReactNode };
17
+
18
+ export function CardLinksProvider(props: CardLinksProviderProps) {
19
+ const { links, children } = props;
20
+
21
+ return <CardLinksContext.Provider value={links}>{children}</CardLinksContext.Provider>;
22
+ }
23
+
24
+ export function useCardLinks(): CardLinks | null {
25
+ return useContext(CardLinksContext);
26
+ }
27
+
28
+ export type CardContentProviderProps = { shown: ReadonlySet<IRI>; children: ReactNode };
29
+
30
+ // Wraps a card's own content: nested cards get no links of their own, and tables learn what is shown above.
31
+ export function CardContentProvider(props: CardContentProviderProps) {
32
+ const { shown, children } = props;
33
+
34
+ return (
35
+ <CardLinksContext.Provider value={null}>
36
+ <LinksShownAboveContext.Provider value={shown}>{children}</LinksShownAboveContext.Provider>
37
+ </CardLinksContext.Provider>
38
+ );
39
+ }
40
+
41
+ export function useLinksShownAbove(): ReadonlySet<IRI> {
42
+ return useContext(LinksShownAboveContext);
43
+ }
@@ -32,6 +32,7 @@ export const BrandAssetAnalysisView = memo(function BrandAssetAnalysisView(props
32
32
  icon={ScanEye}
33
33
  label={`Asset Analysis: ${displayName}`}
34
34
  labelMaxWidth="max-w-140"
35
+ omitPredicates={listedElsewhere}
35
36
  selected={selected}
36
37
  ringColor={ringColor}
37
38
  onContextMenu={onContextMenu}
@@ -101,6 +101,7 @@ export const BrandFontSuggestionView = memo(function BrandFontSuggestionView(pro
101
101
  icon={Type}
102
102
  label={`Font Suggestion: ${family}`}
103
103
  labelMaxWidth="max-w-140"
104
+ omitPredicates={listedElsewhere}
104
105
  selected={selected}
105
106
  ringColor={ringColor}
106
107
  onContextMenu={onContextMenu}
@@ -1,5 +1,10 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
1
3
  import type { LucideIcon } from "lucide-react";
2
- import { memo, type MouseEvent, type ReactNode } from "react";
4
+ import { memo, type MouseEvent, type ReactNode, useMemo } from "react";
5
+ import { CardContentProvider, useCardLinks } from "../card-links";
6
+ import { RelatedSection } from "../special-nodes/RelatedSection";
7
+ import type { NodeRelation } from "../types";
3
8
  import { cn } from "#style";
4
9
 
5
10
  export type EntityCardShellProps = {
@@ -9,11 +14,31 @@ export type EntityCardShellProps = {
9
14
  selected?: boolean;
10
15
  ringColor?: string;
11
16
  onContextMenu?: (e: MouseEvent) => void;
17
+ // Fields the card body shows itself (an image, a weights list), so the links section leaves them out too.
18
+ omitPredicates?: readonly IRI[];
12
19
  children: ReactNode;
13
20
  };
14
21
 
22
+ const noRelations: NodeRelation[] = [];
23
+
15
24
  export const EntityCardShell = memo(function EntityCardShell(props: EntityCardShellProps) {
16
- const { icon: Icon, label, labelMaxWidth = "max-w-50", selected = false, ringColor, onContextMenu, children } = props;
25
+ const {
26
+ icon: Icon,
27
+ label,
28
+ labelMaxWidth = "max-w-50",
29
+ selected = false,
30
+ ringColor,
31
+ onContextMenu,
32
+ omitPredicates,
33
+ children,
34
+ } = props;
35
+ const links = useCardLinks();
36
+
37
+ const listed = useMemo(() => {
38
+ const omitted = new Set(omitPredicates);
39
+ return (links?.relations ?? noRelations).filter((relation) => !omitted.has(relation.predicate));
40
+ }, [links, omitPredicates]);
41
+ const shown = useMemo(() => new Set(listed.map((relation) => relation.target)), [listed]);
17
42
 
18
43
  return (
19
44
  <div className="w-fit flex flex-col items-start gap-1" onContextMenu={onContextMenu}>
@@ -34,7 +59,16 @@ export const EntityCardShell = memo(function EntityCardShell(props: EntityCardSh
34
59
  )}
35
60
  style={selected && ringColor ? { boxShadow: `0 0 0 1.5px ${ringColor}` } : undefined}
36
61
  >
37
- {children}
62
+ <CardContentProvider shown={shown}>
63
+ {isPresent(listed) ? (
64
+ <div className="overflow-hidden rounded-md bg-muted">
65
+ <RelatedSection relations={listed} />
66
+ {children}
67
+ </div>
68
+ ) : (
69
+ children
70
+ )}
71
+ </CardContentProvider>
38
72
  </div>
39
73
  </div>
40
74
  );
@@ -1,4 +1,7 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import type { IRI } from "@manta-eu/rdf-lib/namespaces";
1
3
  import { useCallback, useMemo } from "react";
4
+ import { useLinksShownAbove } from "../card-links";
2
5
  import { shouldHidePreviewField } from "../graph-list-utils";
3
6
  import { AttributeList, useAttributeFacts } from "../special-nodes/AttributeList";
4
7
  import type { NodeProperty, NodeRelation } from "../types";
@@ -17,7 +20,7 @@ export type NodePropertyTableProps = {
17
20
  // The node's raw values, so a multi-valued property renders as tags rather than a joined string.
18
21
  jsonLd?: Record<string, unknown>;
19
22
  // Rendered above the table by the card itself, so the table skips them.
20
- omitPredicates?: readonly string[];
23
+ omitPredicates?: readonly IRI[];
21
24
  relations?: NodeRelation[];
22
25
  };
23
26
 
@@ -45,6 +48,17 @@ export function NodePropertyTable(props: NodePropertyTableProps) {
45
48
  );
46
49
 
47
50
  const { facts, previewCount } = useAttributeFacts({ properties, relations: visibleRelations, jsonLd, include });
51
+ const shownAbove = useLinksShownAbove();
52
+ const shown = useMemo(
53
+ () =>
54
+ facts.flatMap((fact) => {
55
+ if (!isPresent(fact.entityIris)) return [fact];
56
+
57
+ const rest = fact.entityIris.filter((iri) => !shownAbove.has(iri));
58
+ return isPresent(rest) ? [{ ...fact, entityIris: rest }] : [];
59
+ }),
60
+ [facts, shownAbove],
61
+ );
48
62
 
49
- return <AttributeList facts={facts} previewCount={previewCount} showAll />;
63
+ return <AttributeList facts={shown} previewCount={previewCount} showAll />;
50
64
  }
@@ -1,3 +1,4 @@
1
+ import { isHexColor } from "@manta-eu/lib/color";
1
2
  import { formatPercent, parseNumeric } from "../property-formatters";
2
3
 
3
4
  export type PropertyDisplay =
@@ -9,12 +10,6 @@ export type PropertyDisplay =
9
10
  | { kind: "prose"; text: string }
10
11
  | { kind: "text"; text: string };
11
12
 
12
- const hexPattern = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
13
-
14
- // Shared with the palette swatch: both guard a style sink, so the two must not drift.
15
- export function isHexColor(value: string): boolean {
16
- return hexPattern.test(value);
17
- }
18
13
  const prosePhraseLength = 60;
19
14
 
20
15
  // Wider than formatDisplayValue's isPercentLabel, which excludes score — merging them would change both.
@@ -36,7 +31,7 @@ export function classifyPropertyDisplay(args: { label: string; formatted: string
36
31
  if (values.length > 1) return { kind: "tags", values };
37
32
  }
38
33
 
39
- if (hexPattern.test(formatted)) return { kind: "color", hex: formatted };
34
+ if (isHexColor(formatted)) return { kind: "color", hex: formatted };
40
35
 
41
36
  // RDF booleans arrive as the literals "true"/"false" from the graph, as real booleans from fixtures.
42
37
  if (raw === true || raw === false) return { kind: "boolean", value: raw };
@@ -97,8 +97,10 @@ export function AttributeList(props: AttributeListProps) {
97
97
  );
98
98
 
99
99
  const visibleFacts = searching || expanded ? matchingFacts : facts.slice(0, previewCount);
100
- const hasHiddenChips = visibleFacts.some((fact) => visibleChips(fact.entityIris, expanded).hiddenCount > 0);
101
100
  const hiddenCount = facts.length - previewCount;
101
+ const collapsible =
102
+ hiddenCount > 0 ||
103
+ facts.slice(0, previewCount).some((fact) => visibleChips(fact.entityIris, false).hiddenCount > 0);
102
104
 
103
105
  if (!isPresent(facts)) return null;
104
106
 
@@ -161,7 +163,7 @@ export function AttributeList(props: AttributeListProps) {
161
163
  {searching && !isPresent(visibleFacts) && (
162
164
  <span className="typography-tiny py-1 text-muted-foreground">No matching attributes.</span>
163
165
  )}
164
- {!showAll && (hiddenCount > 0 || hasHiddenChips || expanded) && (
166
+ {!showAll && collapsible && (
165
167
  <NodePropertyExpander
166
168
  expanded={expanded}
167
169
  collapsedLabel={hiddenCount > 0 ? `Show all ${facts.length}` : "Show all"}
@@ -0,0 +1,29 @@
1
+ import { isPresent } from "@manta-eu/lib/present";
2
+ import { useMemo } from "react";
3
+ import type { NodeProperty, NodeRelation } from "../types";
4
+ import { AttributeList, useAttributeFacts } from "./AttributeList";
5
+ import { singleLinksFirst } from "./card-facts";
6
+
7
+ export type RelatedSectionProps = { relations: NodeRelation[] };
8
+
9
+ const noProperties: NodeProperty[] = [];
10
+ const noJsonLd: Record<string, unknown> = {};
11
+
12
+ export function RelatedSection(props: RelatedSectionProps) {
13
+ const { relations } = props;
14
+
15
+ const { facts, previewCount } = useAttributeFacts({ properties: noProperties, relations, jsonLd: noJsonLd });
16
+ const ordered = useMemo(() => singleLinksFirst(facts), [facts]);
17
+
18
+ if (!isPresent(ordered)) return null;
19
+
20
+ return (
21
+ <div
22
+ role="group"
23
+ aria-label="Related records"
24
+ className="flex max-w-90 flex-col border-b border-muted-border px-3 pb-2 pt-2.5 text-default-foreground"
25
+ >
26
+ <AttributeList facts={ordered} previewCount={previewCount} />
27
+ </div>
28
+ );
29
+ }
@@ -107,6 +107,19 @@ export function keyWords(key: string): string[] {
107
107
  .map((word) => word.toLowerCase());
108
108
  }
109
109
 
110
+ // Links to records with a name of their own, so unnamed sub-records (an insight's action rows) stay off the card.
111
+ export function namedRelations(relations: NodeRelation[], instanceLabels: Map<string, string | null>): NodeRelation[] {
112
+ return relations.filter((relation) => isPresent(instanceLabels.get(relation.target)));
113
+ }
114
+
115
+ // What the record belongs to (one campaign, one account) reads before what it has many of.
116
+ export function singleLinksFirst(facts: CardFact[]): CardFact[] {
117
+ return [
118
+ ...facts.filter((fact) => fact.entityIris.length <= 1),
119
+ ...facts.filter((fact) => fact.entityIris.length > 1),
120
+ ];
121
+ }
122
+
110
123
  export function relationFacts(
111
124
  relations: NodeRelation[],
112
125
  terms: Map<string, OntologyTerm>,
@@ -11,7 +11,7 @@ export type NodeProperty = {
11
11
  };
12
12
 
13
13
  // A property whose value is another entity in the graph; the canvas draws it as an edge instead.
14
- export type NodeRelation = { predicate: string; target: string };
14
+ export type NodeRelation = { predicate: IRI; target: IRI };
15
15
 
16
16
  export type RDFNodeData = {
17
17
  label: string;
@@ -1,4 +1,6 @@
1
1
  import { useExecuteAgentActionAdditionMutation } from "@manta-eu/client";
2
+ import { entityActionInput } from "@manta-eu/client/shared/action-document";
3
+ import { BrandAssetDecisionSchema } from "@manta-eu/ldo/schemas/brand";
2
4
  import { isPresent } from "@manta-eu/lib/present";
3
5
  import { isValidFullIri, namespaces } from "@manta-eu/rdf-lib/namespaces";
4
6
  import { useQueryClient } from "@tanstack/react-query";
@@ -8,13 +10,12 @@ import { useCallback, useMemo, useRef, useState } from "react";
8
10
  import { toast } from "sonner";
9
11
  import type { ManualAction } from "./use-manual-actions";
10
12
  import { useSparqlSelect } from "./use-sparql-construct";
11
- import { buildActionDocument, mintSubjectIri } from "#lib/action-document";
13
+ import { mintSubjectIri } from "#lib/action-document";
12
14
  import { useMantaClient } from "#providers/MantaClientProvider";
13
15
  import { invalidateActionPlanQueries } from "#shared/query-keys";
14
16
 
15
17
  const df = new DataFactory();
16
18
  const assetAnalysis = `${namespaces.brand}AssetAnalysis`;
17
- const assetDecision = `${namespaces.brand}AssetDecision`;
18
19
  const decideAssetShape = `${namespaces.brand}DecideAssetShape`;
19
20
 
20
21
  export type ApproveAnywayHandle = {
@@ -68,16 +69,12 @@ export function useApproveAnyway(planGraphIri: string): ApproveAnywayHandle {
68
69
  if (inFlight.current.has(analysisIri)) return;
69
70
  inFlight.current.add(analysisIri);
70
71
  setApproving(new Set(inFlight.current));
71
- const subjectIri = mintSubjectIri(assetDecision);
72
- const properties = buildActionDocument(
73
- { subjectIri, rdfType: assetDecision },
74
- {
75
- [`${namespaces.brand}decisionOnAsset`]: { "@id": analysisIri },
76
- [`${namespaces.brand}decision`]: "approved",
77
- },
78
- );
79
72
  void startAddition({
80
- input: { subjectIri, rdfType: assetDecision, actionShape: decideAssetShape, properties },
73
+ input: entityActionInput(BrandAssetDecisionSchema, decideAssetShape, {
74
+ $id: mintSubjectIri(BrandAssetDecisionSchema["@type"]),
75
+ decisionOnAsset: analysisIri,
76
+ decision: "approved",
77
+ }),
81
78
  })
82
79
  .then(({ executeActionAddition }) => {
83
80
  if (!executeActionAddition.success) throw new Error(executeActionAddition.error ?? "The approval failed");
package/src/ldo-ui.ts CHANGED
@@ -344,6 +344,7 @@ export {
344
344
  export { NodeFrame, type NodeFrameProps } from "./graph/special-nodes/NodeFrame";
345
345
 
346
346
  export { EntityCard, findEntityNode, type EntityCardProps } from "./graph/EntityCard";
347
+ export { EntityViewWithLinks, type EntityViewWithLinksProps } from "./graph/EntityViewWithLinks";
347
348
  export { findDetailViewForTypes, findPluralViewForTypes, type DetailView, type PluralView } from "./graph/detail-views";
348
349
  export { EntityStripDisplay, type EntityStripDisplayProps } from "./graph/EntityStripDisplay";
349
350
  export { stubNodeData } from "./graph/stub-node";
@@ -3,5 +3,5 @@ import { isStagedManageRulesResult } from "../components/tool-invocation-card/ty
3
3
  import { findLatestToolOutput } from "./find-latest-tool-output";
4
4
 
5
5
  export function findLatestStagedRuleIri(messages: ChatUIMessage[]): string | null {
6
- return findLatestToolOutput(messages, "tool-manageRDFoxRules", isStagedManageRulesResult)?.ruleIri ?? null;
6
+ return findLatestToolOutput(messages, "tool-manageRules", isStagedManageRulesResult)?.ruleIri ?? null;
7
7
  }
@@ -27,12 +27,12 @@ import {
27
27
  import { match } from "ts-pattern";
28
28
 
29
29
  const toolIconMap: Record<ToolName, LucideIcon> = {
30
- getRDFoxFacts: Database,
31
- addRDFoxFacts: Database,
32
- manageRDFoxFacts: Database,
33
- getRDFoxRules: Brain,
34
- explainRDFoxRules: Brain,
35
- manageRDFoxRules: Brain,
30
+ queryKnowledgeGraph: Database,
31
+ addFacts: Database,
32
+ manageFacts: Database,
33
+ getRules: Brain,
34
+ explainFact: Brain,
35
+ manageRules: Brain,
36
36
  generateChart: TrendingUp,
37
37
  scheduleTask: Clock,
38
38
  listScheduledTasks: Clock,
@@ -1,7 +1,6 @@
1
1
  export function formatToolName(toolType: string): string {
2
2
  const name = toolType.replace("tool-", "");
3
3
  return name
4
- .replace(/RDFox/gi, "")
5
4
  .replace(/([A-Z])/g, " $1")
6
5
  .replace(/^./, (str) => str.toUpperCase())
7
6
  .replace(/\s+/g, " ")
@@ -5,7 +5,7 @@ export type SkolemPart = { type: "iri"; value: string } | { type: "literal"; val
5
5
  const blankNodePrefixes = ["_:", "_."];
6
6
  const segmentTagLength = 2;
7
7
 
8
- // RDFox datatype tags: "02" = IRI, "05" = string; other datatypes are binary-encoded.
8
+ // The datastore datatype tags: "02" = IRI, "05" = string; other datatypes are binary-encoded.
9
9
  const segmentTypeByTag: Record<string, SkolemPart["type"]> = {
10
10
  "02": "iri",
11
11
  "05": "literal",
@@ -29,7 +29,7 @@ function decodeSegment(segment: string): string | undefined {
29
29
  }
30
30
  }
31
31
 
32
- // RDFox skolem labels: dot-separated base64 segments, each prefixed with a 2-char type tag.
32
+ // The datastore skolem labels: dot-separated base64 segments, each prefixed with a 2-char type tag.
33
33
  export function decodeSkolem(term: string): SkolemPart[] | undefined {
34
34
  const prefix = blankNodePrefixes.find((candidate) => term.startsWith(candidate));
35
35
 
@@ -135,7 +135,7 @@ export function KnowledgeGraphStateProvider(props: KnowledgeGraphStateProviderPr
135
135
 
136
136
  if (currentDataVersion && isPresent(previousDataVersion) && previousDataVersion !== currentDataVersion) {
137
137
  void invalidateGraphDerivedQueries(queryClient);
138
- console.debug("Knowledge graph dataVersion changed, invalidating RDFox-related queries");
138
+ console.debug("Knowledge graph dataVersion changed, invalidating the knowledge graph queries");
139
139
  }
140
140
 
141
141
  if (currentDataVersion && currentDataVersion !== previousDataVersion) {
@@ -1,4 +1,4 @@
1
- import { ontologyGraphIri, shaclGraphIri } from "@manta-eu/client/shared/rdf-namespaces";
1
+ import { ontologyGraphIri, shaclGraph } from "@manta-eu/client/shared/rdf-namespaces";
2
2
  import { isPresent } from "@manta-eu/lib/present";
3
3
  import { DataFactory } from "ldkit/rdf";
4
4
  import { SELECT } from "ldkit/sparql";
@@ -32,7 +32,7 @@ function classPropertiesQuery(classIri: string): string {
32
32
 
33
33
  function shapeConstraintsQuery(classIri: string): string {
34
34
  return `${prefixes}${SELECT`?path ?minCount ?allowedValue`.WHERE`
35
- VALUES ?shapeGraph { ${df.namedNode(ontologyGraphIri)} ${df.namedNode(shaclGraphIri)} }
35
+ VALUES ?shapeGraph { ${df.namedNode(ontologyGraphIri)} ${df.namedNode(shaclGraph)} }
36
36
  GRAPH ?shapeGraph {
37
37
  ?shape sh:targetClass ${df.namedNode(safeIri(classIri))} ; sh:property ?propertyShape .
38
38
  ?propertyShape sh:path ?path .
@@ -1,4 +1,4 @@
1
- import { ontologyGraphIri, shaclGraphIri } from "@manta-eu/client/shared/rdf-namespaces";
1
+ import { ontologyGraphIri, shaclGraph } from "@manta-eu/client/shared/rdf-namespaces";
2
2
  import { isPresent } from "@manta-eu/lib/present";
3
3
  import { DataFactory, type RDF } from "ldkit/rdf";
4
4
  import { SELECT } from "ldkit/sparql";
@@ -33,7 +33,7 @@ const classesQuery = `${prefixes}${SELECT.DISTINCT`?class ?label ?comment`.WHERE
33
33
  }
34
34
  }
35
35
  UNION {
36
- GRAPH ${df.namedNode(shaclGraphIri)} { ?shapeInShaclGraph sh:targetClass ?class }
36
+ GRAPH ${df.namedNode(shaclGraph)} { ?shapeInShaclGraph sh:targetClass ?class }
37
37
  }
38
38
  OPTIONAL { GRAPH ${df.namedNode(ontologyGraphIri)} { ?class rdfs:label ?label } }
39
39
  OPTIONAL { GRAPH ${df.namedNode(ontologyGraphIri)} { ?class rdfs:comment ?comment } }
@@ -59,7 +59,6 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
59
59
  const baselineRef = useRef<PropertyMap | null>(null);
60
60
  const [refusal, setRefusal] = useState<string | null>(null);
61
61
  const [formElement, setFormElement] = useState<ShaclFormElement | null>(null);
62
- const [isReadingForm, setIsReadingForm] = useState(false);
63
62
 
64
63
  const attachForm = useCallback((element: ShaclFormElement | null) => setFormElement(element), []);
65
64
 
@@ -77,29 +76,20 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
77
76
  useEffect(() => {
78
77
  if (!formElement || !isUpdate) return;
79
78
 
80
- let cancelled = false;
81
-
82
79
  // Baseline is the form's own serialization: its editors normalize literals, the triples do not.
83
- async function captureBaseline() {
80
+ function onReady() {
84
81
  const store = formElement?.toRDF();
85
- const baseline = store ? await baselineFromStore(store, subjectIri) : null;
86
-
87
- if (baseline && !cancelled) baselineRef.current = baseline;
88
- }
82
+ const baseline = store ? baselineFromStore(store, subjectIri) : null;
89
83
 
90
- function onReady() {
91
- void captureBaseline();
84
+ if (baseline) baselineRef.current = baseline;
92
85
  }
93
86
 
94
87
  onReady();
95
88
  formElement.addEventListener("ready", onReady);
96
- return () => {
97
- cancelled = true;
98
- formElement.removeEventListener("ready", onReady);
99
- };
89
+ return () => formElement.removeEventListener("ready", onReady);
100
90
  }, [formElement, isUpdate, subjectIri]);
101
91
 
102
- async function submit() {
92
+ function submit() {
103
93
  const store = formElement?.toRDF();
104
94
  const baseline = baselineRef.current;
105
95
 
@@ -108,14 +98,7 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
108
98
  return;
109
99
  }
110
100
 
111
- setIsReadingForm(true);
112
- const edited = await propertiesFromStore(store, subjectIri).catch(() => null);
113
- setIsReadingForm(false);
114
-
115
- if (!edited) {
116
- setRefusal("The form could not be read.");
117
- return;
118
- }
101
+ const edited = propertiesFromStore(store, subjectIri);
119
102
 
120
103
  if (isUpdate && baseline) {
121
104
  const cleared = removedPredicates(baseline, edited);
@@ -176,12 +159,7 @@ export function ActionFormDialog(props: ActionFormDialogProps) {
176
159
  <Button variant="tertiary" size="sm" onClick={() => onOpenChange(false)}>
177
160
  Cancel
178
161
  </Button>
179
- <Button
180
- size="sm"
181
- onClick={() => void submit()}
182
- isLoading={isPending || isReadingForm}
183
- disabled={!shapesTurtle}
184
- >
162
+ <Button size="sm" onClick={submit} isLoading={isPending} disabled={!shapesTurtle}>
185
163
  {copy.submitLabel}
186
164
  </Button>
187
165
  </DialogFooter>
@@ -7,6 +7,7 @@ import { actionShapesForTypes } from "./action-shapes";
7
7
  import { Badge } from "#components/badge/index";
8
8
  import {
9
9
  type ActionShapeSpec,
10
+ EntityViewWithLinks,
10
11
  findEntityNode,
11
12
  GenericView,
12
13
  getEntityView,
@@ -97,7 +98,11 @@ export function ChatPreview(props: ChatPreviewProps) {
97
98
  <section>
98
99
  <h3 className={cn(sectionLabelClass, "mb-1.5 text-muted-foreground")}>Chat rendering</h3>
99
100
  <div className="max-w-md">
100
- {EntityView ? <EntityView data={node.data} /> : <GenericView data={node.data} defaultExpanded />}
101
+ {EntityView ? (
102
+ <EntityViewWithLinks view={EntityView} data={node.data} />
103
+ ) : (
104
+ <GenericView data={node.data} defaultExpanded />
105
+ )}
101
106
  </div>
102
107
  </section>
103
108
 
@@ -7,14 +7,14 @@ export type PropertyMap = Record<string, unknown>;
7
7
  // The form stamps this on every serialization
8
8
  const conformsTo = `${namespaces.dcterms}conformsTo`;
9
9
 
10
- export async function propertiesFromStore(store: ShaclFormStore, subjectIri: string): Promise<PropertyMap> {
10
+ export function propertiesFromStore(store: ShaclFormStore, subjectIri: string): PropertyMap {
11
11
  const quads = [...store].filter((quad) => quad.predicate.value !== conformsTo);
12
- const { "@id": _id, "@type": _type, ...properties } = await documentFromQuads(quads, subjectIri);
12
+ const { "@id": _id, "@type": _type, ...properties } = documentFromQuads(quads, subjectIri);
13
13
 
14
14
  return properties;
15
15
  }
16
16
 
17
- export async function baselineFromStore(store: ShaclFormStore, subjectIri: string): Promise<PropertyMap | null> {
17
+ export function baselineFromStore(store: ShaclFormStore, subjectIri: string): PropertyMap | null {
18
18
  if (store.size === 0) return null;
19
19
 
20
20
  return propertiesFromStore(store, subjectIri);
@@ -19,7 +19,7 @@ const df = new DataFactory();
19
19
 
20
20
  export const recentTypesLimit = 10;
21
21
 
22
- // STR() ordering because sources mix xsd:date and xsd:dateTime, which RDFox will not compare as temporals
22
+ // STR() ordering because sources mix xsd:date and xsd:dateTime, which the datastore will not compare as temporals
23
23
  const recentlyModifiedTypesSubquery = SELECT`?type (MAX(STR(?modified)) AS ?lastModified)`.WHERE`
24
24
  ?dated schema:dateModified ?modified .
25
25
  ?dated a ?type .
@@ -1,4 +1,4 @@
1
- import { ontologyGraphIri, shaclGraphIri } from "@manta-eu/client/shared/rdf-namespaces";
1
+ import { ontologyGraphIri, shaclGraph } from "@manta-eu/client/shared/rdf-namespaces";
2
2
  import { isPresent } from "@manta-eu/lib/present";
3
3
  import { DataFactory, type RDF } from "ldkit/rdf";
4
4
  import { SELECT } from "ldkit/sparql";
@@ -62,7 +62,7 @@ export function groupShapeConstraints(rows: ShapeConstraintRow[]): ClassShape[]
62
62
 
63
63
  function shapeDetailsQuery(classIri: string): string {
64
64
  return `${prefixes}${SELECT.DISTINCT`?shape ?path ?constraint ?value ?listValue`.WHERE`
65
- VALUES ?shapeGraph { ${df.namedNode(ontologyGraphIri)} ${df.namedNode(shaclGraphIri)} }
65
+ VALUES ?shapeGraph { ${df.namedNode(ontologyGraphIri)} ${df.namedNode(shaclGraph)} }
66
66
  GRAPH ?shapeGraph {
67
67
  ?shape sh:targetClass ${df.namedNode(safeIri(classIri))} ; sh:property ?propertyShape .
68
68
  ?propertyShape sh:path ?path ; ?constraint ?value .
@@ -22,7 +22,7 @@ export type DatastoreRow = {
22
22
  // Org roots carry their own datastore but are also the container the rest hang off.
23
23
  isOrganization: boolean;
24
24
  mirrored?: boolean;
25
- // Real in RDFox, but no Keycloak node behind it, so it can only be selected.
25
+ // Real in the datastore, but no Keycloak node behind it, so it can only be selected.
26
26
  isRealmDefault?: boolean;
27
27
  isUnlinked?: boolean;
28
28
  };
@@ -22,6 +22,8 @@ export type OAuthConnectionMethod = ConnectionMethodBase & {
22
22
  source: "oauth";
23
23
  integration: GQLIntegration;
24
24
  callbackProvider?: string;
25
+ // Further oauth_provider values whose callback resumes this same method, e.g. a second identity provider.
26
+ alternateCallbackProviders?: readonly string[];
25
27
  resumesSetupAfterOAuth?: boolean;
26
28
  connectedAction?: ComponentType;
27
29
  };
@@ -0,0 +1,26 @@
1
+ import { GQLIntegration } from "@manta-eu/client";
2
+ import { OAuthSingleAccountConnectForm } from "./OAuthSingleAccountConnectForm";
3
+ import type { ConnectFormProps } from "./types";
4
+
5
+ const canvaPermissions = [
6
+ "Read your designs, the folders they live in, and your uploaded assets.",
7
+ "Export your designs so their pages can be analyzed.",
8
+ "Read your brand templates and the comments on your designs.",
9
+ ];
10
+
11
+ export function CanvaConnectForm(props: ConnectFormProps) {
12
+ return (
13
+ <OAuthSingleAccountConnectForm
14
+ {...props}
15
+ integration={GQLIntegration.Canva}
16
+ startHref="/oauth/canva/start"
17
+ ctaLabel="Continue with Canva"
18
+ stepTitle="Connect your Canva account"
19
+ stepDescription="Manta will take you to Canva to ask for permission, then bring you back here."
20
+ supportingInfo="You'll be taken to Canva to approve access. You can revoke it any time from your Canva account settings."
21
+ permissions={canvaPermissions}
22
+ waitingLabel="Waiting for you to finish connecting to Canva…"
23
+ connectedToastLabel="Canva connected"
24
+ />
25
+ );
26
+ }