@ayni.org/ayni-ui-core 0.2.0 → 0.3.1

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 CHANGED
@@ -10,5 +10,19 @@ and sync. The browser UI in this repository and the Ayni app both build on it; n
10
10
  pnpm add @ayni.org/ayni-ui-core @ayni.org/ayni-agui-profile
11
11
  ```
12
12
 
13
+ ## One design, two renderers
14
+
15
+ The browser draws with CSS and the Ayni app with React Native styles. What the two must share is written here once:
16
+
17
+ | Surface | Design values and colour roles | What it shows |
18
+ | --- | --- | --- |
19
+ | A conversation | `CONVERSATION_DESIGN`, `CONVERSATION_COLORS` | `replyQuoteOf`, `reactionPillOf`, `plainText` |
20
+ | The home page (rail and column) | `HOME_DESIGN`, `HOME_COLORS` | `TRUST_MARK`, `CONTROLLER_KIND_ICON`, `formatTime` |
21
+ | The Proof of Trust | `PROOF_OF_TRUST_DESIGN`, `PROOF_OF_TRUST_COLORS` | `verdictOf`, `identityStepOf`, `accreditationLine`, `otherCredentialLine`, `claimRows`, and its English wording (`PROOF_OF_TRUST_COPY`) |
22
+
23
+ The browser's stylesheet is the reference: tests in this repository compare it, and the English translation file,
24
+ with these values, so a change made on one side only fails. The app builds its styles from the same values. A change
25
+ of look or wording on one of these surfaces starts here.
26
+
13
27
  Published to npm from this repository by the `npm-publish` workflow whenever `package.json` carries a version that is
14
28
  not on npm yet.
@@ -78,7 +78,7 @@ export declare const CONVERSATION_DESIGN: {
78
78
  };
79
79
  export type ConversationDesign = typeof CONVERSATION_DESIGN;
80
80
  /** The colour roles both palettes define, in the browser as custom properties and in the app as theme colours. */
81
- export type ColorRole = 'bg' | 'surface' | 'text' | 'muted' | 'border' | 'accent' | 'accentContrast' | 'warning';
81
+ export type ColorRole = 'bg' | 'surface' | 'text' | 'muted' | 'border' | 'accent' | 'accentContrast' | 'warning' | 'ok' | 'danger';
82
82
  /** Which colour role paints what in a conversation. */
83
83
  export declare const CONVERSATION_COLORS: {
84
84
  readonly userBubble: {
package/dist/flag.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** The flag emoji of an ISO 3166-1 alpha-2 code, or an empty string when the code is not one. */
2
+ export declare function flagOf(countryCode?: string): string;
package/dist/flag.js ADDED
@@ -0,0 +1,7 @@
1
+ /** The flag emoji of an ISO 3166-1 alpha-2 code, or an empty string when the code is not one. */
2
+ export function flagOf(countryCode) {
3
+ const code = (countryCode ?? '').trim().toUpperCase();
4
+ if (!/^[A-Z]{2}$/.test(code))
5
+ return '';
6
+ return String.fromCodePoint(...[...code].map((c) => 0x1f1e6 + c.charCodeAt(0) - 65));
7
+ }
@@ -0,0 +1,270 @@
1
+ /** The text every surface starts from: its size, and its line height as a multiple of the size. */
2
+ export declare const BASE_TEXT: {
3
+ readonly fontSize: 15;
4
+ readonly lineHeight: 1.45;
5
+ };
6
+ export declare const HOME_DESIGN: {
7
+ readonly rail: {
8
+ readonly width: 64;
9
+ readonly paddingY: 10;
10
+ readonly gap: 6;
11
+ /** a controller's tile, and the bordered tiles that are not controllers (add, settings) */
12
+ readonly tile: {
13
+ readonly size: 48;
14
+ readonly radius: 14;
15
+ readonly initialsFontSize: 15;
16
+ readonly initialsWeight: 600;
17
+ readonly inactiveOpacity: 0.5;
18
+ };
19
+ readonly ghost: {
20
+ readonly borderWidth: 1;
21
+ readonly iconSize: 22;
22
+ };
23
+ /** the bar to the left of the selected tile */
24
+ readonly selectedBar: {
25
+ readonly left: -8;
26
+ readonly inset: 10;
27
+ readonly width: 4;
28
+ readonly radius: 2;
29
+ };
30
+ /** unread messages, on the tile's top right corner */
31
+ readonly badge: {
32
+ readonly top: 2;
33
+ readonly right: 2;
34
+ readonly minWidth: 18;
35
+ readonly height: 18;
36
+ readonly paddingX: 5;
37
+ readonly radius: 9;
38
+ readonly fontSize: 11;
39
+ readonly fontWeight: 600;
40
+ };
41
+ /** something waits for the person, on the tile's bottom right corner */
42
+ readonly dot: {
43
+ readonly bottom: 4;
44
+ readonly right: 4;
45
+ readonly size: 9;
46
+ readonly borderWidth: 2;
47
+ };
48
+ /** the line under the pinned tiles, with its pin */
49
+ readonly separator: {
50
+ readonly width: 48;
51
+ readonly height: 14;
52
+ readonly lineWidth: 1;
53
+ readonly pinSize: 11;
54
+ readonly pinPaddingX: 3;
55
+ };
56
+ /**
57
+ * The two zones a tile is dragged between, the pinned one above the line and the rest below: while a tile is
58
+ * dragged both show a dashed outline, the one under the pointer a solid one; the empty pinned zone grows to
59
+ * one slot.
60
+ */
61
+ readonly zone: {
62
+ readonly minHeight: 12;
63
+ readonly radius: 12;
64
+ readonly droppableMinHeight: 48;
65
+ readonly droppableOutlineWidth: 1;
66
+ /** the share of the accent in the dashed outline */
67
+ readonly droppableOutlineShare: 0.5;
68
+ readonly outlineOffset: -2;
69
+ readonly targetOutlineWidth: 2;
70
+ };
71
+ /** the tile being dragged, where it stood; and the line above the pinned tile it would land before */
72
+ readonly drag: {
73
+ readonly opacity: 0.4;
74
+ readonly scale: 0.96;
75
+ readonly dropLineWidth: 3;
76
+ };
77
+ };
78
+ readonly column: {
79
+ readonly header: {
80
+ readonly height: 64;
81
+ readonly paddingLeft: 12;
82
+ readonly paddingRight: 8;
83
+ readonly gap: 8;
84
+ readonly fontSize: 16;
85
+ readonly iconSize: 18;
86
+ readonly flagFontSize: 16;
87
+ };
88
+ /** one agent: its logo, its name with the trust mark, its links, its menu */
89
+ readonly agentRow: {
90
+ readonly gap: 10;
91
+ readonly paddingTop: 8;
92
+ readonly paddingRight: 6;
93
+ readonly paddingBottom: 8;
94
+ readonly paddingLeft: 10;
95
+ };
96
+ readonly agentAvatar: {
97
+ readonly size: 40;
98
+ readonly radius: 12;
99
+ readonly initialsFontSize: 14;
100
+ };
101
+ readonly agentText: {
102
+ readonly gap: 1;
103
+ };
104
+ readonly agentTitle: {
105
+ readonly gap: 6;
106
+ readonly fontWeight: 600;
107
+ };
108
+ readonly trustMark: {
109
+ readonly size: 24;
110
+ readonly radius: 6;
111
+ readonly iconSize: 16;
112
+ };
113
+ readonly links: {
114
+ readonly gap: 10;
115
+ readonly fontSize: 11;
116
+ readonly lineHeight: 1.2;
117
+ };
118
+ /** the small icon buttons of a card: its menu, new conversation, search */
119
+ readonly smallButton: {
120
+ readonly size: 36;
121
+ readonly radius: 12;
122
+ readonly iconSize: 18;
123
+ };
124
+ /** the open card: the link state with the tools, then the conversations */
125
+ readonly body: {
126
+ readonly paddingX: 8;
127
+ readonly paddingBottom: 8;
128
+ readonly gap: 6;
129
+ };
130
+ readonly meta: {
131
+ readonly paddingX: 4;
132
+ readonly gap: 8;
133
+ readonly fontSize: 12;
134
+ readonly stateGap: 5;
135
+ readonly iconSize: 12;
136
+ readonly toolsGap: 2;
137
+ };
138
+ readonly conversationRow: {
139
+ readonly paddingY: 10;
140
+ readonly paddingLeft: 12;
141
+ /** room for the row's menu button */
142
+ readonly paddingRight: 40;
143
+ readonly minHeight: 44;
144
+ readonly radius: 10;
145
+ readonly rowGap: 2;
146
+ readonly columnGap: 8;
147
+ readonly titleWeight: 600;
148
+ readonly previewFontSize: 13;
149
+ readonly timeFontSize: 12;
150
+ /** the share of the accent mixed into the background of the selected row */
151
+ readonly selectedTint: 0.12;
152
+ readonly menuButtonSize: 36;
153
+ readonly menuIconSize: 16;
154
+ };
155
+ /** unread messages of a conversation, and of a closed card */
156
+ readonly count: {
157
+ readonly radius: 9;
158
+ readonly fontSize: 11;
159
+ readonly paddingY: 1;
160
+ readonly paddingX: 6;
161
+ };
162
+ readonly sectionLabel: {
163
+ readonly fontSize: 12;
164
+ readonly letterSpacing: 0.04;
165
+ readonly gap: 6;
166
+ readonly paddingTop: 4;
167
+ readonly paddingX: 8;
168
+ readonly iconSize: 12;
169
+ };
170
+ readonly empty: {
171
+ readonly fontSize: 13;
172
+ readonly paddingY: 6;
173
+ readonly paddingX: 8;
174
+ };
175
+ };
176
+ };
177
+ export type HomeDesign = typeof HOME_DESIGN;
178
+ /** Which colour role paints what on the home page. */
179
+ export declare const HOME_COLORS: {
180
+ readonly rail: {
181
+ readonly background: "surface";
182
+ readonly border: "border";
183
+ readonly icon: "muted";
184
+ };
185
+ readonly tileInitials: {
186
+ readonly background: "border";
187
+ readonly text: "text";
188
+ };
189
+ readonly ghost: {
190
+ readonly background: "bg";
191
+ readonly border: "border";
192
+ };
193
+ readonly selectedBar: {
194
+ readonly background: "accent";
195
+ };
196
+ readonly badge: {
197
+ readonly background: "danger";
198
+ };
199
+ readonly dot: {
200
+ readonly background: "warning";
201
+ readonly border: "surface";
202
+ };
203
+ readonly separator: {
204
+ readonly line: "border";
205
+ readonly pin: "muted";
206
+ readonly pinBackground: "surface";
207
+ readonly target: "accent";
208
+ };
209
+ readonly zone: {
210
+ readonly outline: "accent";
211
+ };
212
+ readonly drag: {
213
+ readonly dropLine: "accent";
214
+ };
215
+ readonly column: {
216
+ readonly background: "bg";
217
+ readonly border: "border";
218
+ };
219
+ readonly header: {
220
+ readonly icon: "muted";
221
+ };
222
+ readonly selectedCard: {
223
+ readonly background: "surface";
224
+ };
225
+ readonly links: {
226
+ readonly text: "muted";
227
+ };
228
+ readonly meta: {
229
+ readonly text: "muted";
230
+ };
231
+ readonly conversationRow: {
232
+ readonly preview: "muted";
233
+ readonly time: "muted";
234
+ readonly selectedTitle: "accent";
235
+ };
236
+ readonly count: {
237
+ readonly background: "accent";
238
+ readonly text: "accentContrast";
239
+ };
240
+ readonly sectionLabel: {
241
+ readonly text: "muted";
242
+ };
243
+ };
244
+ /** The trust mark of an agent, and the verdict of its Proof of Trust: one icon and one colour per result. */
245
+ export declare const TRUST_MARK: {
246
+ readonly trusted: {
247
+ readonly icon: "shield-check";
248
+ readonly color: "ok";
249
+ };
250
+ readonly untrusted: {
251
+ readonly icon: "shield-alert";
252
+ readonly color: "danger";
253
+ };
254
+ readonly unverified: {
255
+ readonly icon: "shield-question";
256
+ readonly color: "warning";
257
+ };
258
+ };
259
+ export type TrustResult = keyof typeof TRUST_MARK;
260
+ /** The icon before a controller's name: what stands behind its agents. */
261
+ export declare const CONTROLLER_KIND_ICON: {
262
+ readonly organization: "building";
263
+ readonly persona: "user";
264
+ readonly unverified: "shield-question";
265
+ };
266
+ /**
267
+ * The time on a message and on a conversation row: the hour today, the day and the hour before. An unreadable
268
+ * date gives an empty string.
269
+ */
270
+ export declare function formatTime(iso: string, locale: string, now?: Date): string;
@@ -0,0 +1,118 @@
1
+ /** The text every surface starts from: its size, and its line height as a multiple of the size. */
2
+ export const BASE_TEXT = { fontSize: 15, lineHeight: 1.45 };
3
+ export const HOME_DESIGN = {
4
+ rail: {
5
+ width: 64,
6
+ paddingY: 10,
7
+ gap: 6,
8
+ /** a controller's tile, and the bordered tiles that are not controllers (add, settings) */
9
+ tile: { size: 48, radius: 14, initialsFontSize: 15, initialsWeight: 600, inactiveOpacity: 0.5 },
10
+ ghost: { borderWidth: 1, iconSize: 22 },
11
+ /** the bar to the left of the selected tile */
12
+ selectedBar: { left: -8, inset: 10, width: 4, radius: 2 },
13
+ /** unread messages, on the tile's top right corner */
14
+ badge: { top: 2, right: 2, minWidth: 18, height: 18, paddingX: 5, radius: 9, fontSize: 11, fontWeight: 600 },
15
+ /** something waits for the person, on the tile's bottom right corner */
16
+ dot: { bottom: 4, right: 4, size: 9, borderWidth: 2 },
17
+ /** the line under the pinned tiles, with its pin */
18
+ separator: { width: 48, height: 14, lineWidth: 1, pinSize: 11, pinPaddingX: 3 },
19
+ /**
20
+ * The two zones a tile is dragged between, the pinned one above the line and the rest below: while a tile is
21
+ * dragged both show a dashed outline, the one under the pointer a solid one; the empty pinned zone grows to
22
+ * one slot.
23
+ */
24
+ zone: {
25
+ minHeight: 12,
26
+ radius: 12,
27
+ droppableMinHeight: 48,
28
+ droppableOutlineWidth: 1,
29
+ /** the share of the accent in the dashed outline */
30
+ droppableOutlineShare: 0.5,
31
+ outlineOffset: -2,
32
+ targetOutlineWidth: 2,
33
+ },
34
+ /** the tile being dragged, where it stood; and the line above the pinned tile it would land before */
35
+ drag: { opacity: 0.4, scale: 0.96, dropLineWidth: 3 },
36
+ },
37
+ column: {
38
+ header: { height: 64, paddingLeft: 12, paddingRight: 8, gap: 8, fontSize: 16, iconSize: 18, flagFontSize: 16 },
39
+ /** one agent: its logo, its name with the trust mark, its links, its menu */
40
+ agentRow: { gap: 10, paddingTop: 8, paddingRight: 6, paddingBottom: 8, paddingLeft: 10 },
41
+ agentAvatar: { size: 40, radius: 12, initialsFontSize: 14 },
42
+ agentText: { gap: 1 },
43
+ agentTitle: { gap: 6, fontWeight: 600 },
44
+ trustMark: { size: 24, radius: 6, iconSize: 16 },
45
+ links: { gap: 10, fontSize: 11, lineHeight: 1.2 },
46
+ /** the small icon buttons of a card: its menu, new conversation, search */
47
+ smallButton: { size: 36, radius: 12, iconSize: 18 },
48
+ /** the open card: the link state with the tools, then the conversations */
49
+ body: { paddingX: 8, paddingBottom: 8, gap: 6 },
50
+ meta: { paddingX: 4, gap: 8, fontSize: 12, stateGap: 5, iconSize: 12, toolsGap: 2 },
51
+ conversationRow: {
52
+ paddingY: 10,
53
+ paddingLeft: 12,
54
+ /** room for the row's menu button */
55
+ paddingRight: 40,
56
+ minHeight: 44,
57
+ radius: 10,
58
+ rowGap: 2,
59
+ columnGap: 8,
60
+ titleWeight: 600,
61
+ previewFontSize: 13,
62
+ timeFontSize: 12,
63
+ /** the share of the accent mixed into the background of the selected row */
64
+ selectedTint: 0.12,
65
+ menuButtonSize: 36,
66
+ menuIconSize: 16,
67
+ },
68
+ /** unread messages of a conversation, and of a closed card */
69
+ count: { radius: 9, fontSize: 11, paddingY: 1, paddingX: 6 },
70
+ sectionLabel: { fontSize: 12, letterSpacing: 0.04, gap: 6, paddingTop: 4, paddingX: 8, iconSize: 12 },
71
+ empty: { fontSize: 13, paddingY: 6, paddingX: 8 },
72
+ },
73
+ };
74
+ /** Which colour role paints what on the home page. */
75
+ export const HOME_COLORS = {
76
+ rail: { background: 'surface', border: 'border', icon: 'muted' },
77
+ tileInitials: { background: 'border', text: 'text' },
78
+ ghost: { background: 'bg', border: 'border' },
79
+ selectedBar: { background: 'accent' },
80
+ badge: { background: 'danger' },
81
+ dot: { background: 'warning', border: 'surface' },
82
+ separator: { line: 'border', pin: 'muted', pinBackground: 'surface', target: 'accent' },
83
+ zone: { outline: 'accent' },
84
+ drag: { dropLine: 'accent' },
85
+ column: { background: 'bg', border: 'border' },
86
+ header: { icon: 'muted' },
87
+ selectedCard: { background: 'surface' },
88
+ links: { text: 'muted' },
89
+ meta: { text: 'muted' },
90
+ conversationRow: { preview: 'muted', time: 'muted', selectedTitle: 'accent' },
91
+ count: { background: 'accent', text: 'accentContrast' },
92
+ sectionLabel: { text: 'muted' },
93
+ };
94
+ /** The trust mark of an agent, and the verdict of its Proof of Trust: one icon and one colour per result. */
95
+ export const TRUST_MARK = {
96
+ trusted: { icon: 'shield-check', color: 'ok' },
97
+ untrusted: { icon: 'shield-alert', color: 'danger' },
98
+ unverified: { icon: 'shield-question', color: 'warning' },
99
+ };
100
+ /** The icon before a controller's name: what stands behind its agents. */
101
+ export const CONTROLLER_KIND_ICON = {
102
+ organization: 'building',
103
+ persona: 'user',
104
+ unverified: 'shield-question',
105
+ };
106
+ /**
107
+ * The time on a message and on a conversation row: the hour today, the day and the hour before. An unreadable
108
+ * date gives an empty string.
109
+ */
110
+ export function formatTime(iso, locale, now = new Date()) {
111
+ const d = new Date(iso);
112
+ if (Number.isNaN(d.getTime()))
113
+ return '';
114
+ const today = now.toDateString() === d.toDateString();
115
+ return new Intl.DateTimeFormat(locale, today
116
+ ? { hour: '2-digit', minute: '2-digit' }
117
+ : { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }).format(d);
118
+ }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,10 @@ export * from './agents.js';
2
2
  export * from './appLinks.js';
3
3
  export * from './conversationDesign.js';
4
4
  export * from './directory.js';
5
+ export * from './flag.js';
6
+ export * from './homeDesign.js';
5
7
  export * from './profileClient.js';
8
+ export * from './proofOfTrustDesign.js';
6
9
  export * from './reducer.js';
7
10
  export * from './sse.js';
8
11
  export * from './store.js';
@@ -59,8 +62,6 @@ export declare function controllerOf(agent: {
59
62
  logoRef?: string;
60
63
  countryCode?: string;
61
64
  };
62
- /** The flag emoji of an ISO 3166-1 alpha-2 code, or an empty string when the code is not one. */
63
- export declare function flagOf(countryCode?: string): string;
64
65
  /** Rail order for controllers (UI-SHELL-011): pinned in the person's order, then by activity, then unverified. */
65
66
  /**
66
67
  * The stable rail (UI-SHELL-011, D37): pinned controllers in the person's order, then the identified ones (an
package/dist/index.js CHANGED
@@ -2,7 +2,10 @@ export * from './agents.js';
2
2
  export * from './appLinks.js';
3
3
  export * from './conversationDesign.js';
4
4
  export * from './directory.js';
5
+ export * from './flag.js';
6
+ export * from './homeDesign.js';
5
7
  export * from './profileClient.js';
8
+ export * from './proofOfTrustDesign.js';
6
9
  export * from './reducer.js';
7
10
  export * from './sse.js';
8
11
  export * from './store.js';
@@ -60,13 +63,6 @@ export function controllerOf(agent) {
60
63
  }
61
64
  return { did: agent.did, kind: 'unverified', name: agent.name, logoRef: agent.logoRef };
62
65
  }
63
- /** The flag emoji of an ISO 3166-1 alpha-2 code, or an empty string when the code is not one. */
64
- export function flagOf(countryCode) {
65
- const code = (countryCode ?? '').trim().toUpperCase();
66
- if (!/^[A-Z]{2}$/.test(code))
67
- return '';
68
- return String.fromCodePoint(...[...code].map((c) => 0x1f1e6 + c.charCodeAt(0) - 65));
69
- }
70
66
  /** Rail order for controllers (UI-SHELL-011): pinned in the person's order, then by activity, then unverified. */
71
67
  /**
72
68
  * The stable rail (UI-SHELL-011, D37): pinned controllers in the person's order, then the identified ones (an
@@ -0,0 +1,350 @@
1
+ import { type TrustResult } from './homeDesign.js';
2
+ import type { Accreditation, CredentialCard, TrustDetails } from './trustDetails.js';
3
+ export declare const PROOF_OF_TRUST_DESIGN: {
4
+ /** the card itself; on a phone it takes the screen, with no border and no radius */
5
+ readonly card: {
6
+ readonly maxWidth: 760;
7
+ readonly radius: 18;
8
+ };
9
+ readonly head: {
10
+ readonly gap: 6;
11
+ readonly paddingY: 12;
12
+ readonly paddingRight: 14;
13
+ readonly paddingLeft: 18;
14
+ readonly dotSize: 10;
15
+ readonly didFontSize: 12;
16
+ readonly toolIconSize: 15;
17
+ readonly closeIconSize: 18;
18
+ };
19
+ readonly body: {
20
+ readonly paddingTop: 18;
21
+ readonly paddingX: 22;
22
+ readonly paddingBottom: 22;
23
+ readonly gap: 18;
24
+ };
25
+ readonly verdict: {
26
+ readonly gap: 10;
27
+ readonly paddingY: 12;
28
+ readonly paddingX: 16;
29
+ readonly radius: 14;
30
+ readonly borderWidth: 2;
31
+ readonly iconSize: 22;
32
+ /** the share of the verdict's colour mixed into the background */
33
+ readonly tint: 0.08;
34
+ readonly labelFontSize: 18;
35
+ readonly labelWeight: 700;
36
+ readonly labelSpacing: 0.06;
37
+ readonly subFontSize: 12;
38
+ };
39
+ readonly reasons: {
40
+ readonly fontSize: 14;
41
+ readonly paddingLeft: 18;
42
+ };
43
+ readonly loading: {
44
+ readonly gap: 8;
45
+ readonly fontSize: 13;
46
+ readonly iconSize: 16;
47
+ };
48
+ /** one identity step: its node on the line, then who it is */
49
+ readonly step: {
50
+ readonly gap: 16;
51
+ readonly paddingBottom: 22;
52
+ readonly bodyGap: 8;
53
+ };
54
+ /** the line that joins a step's node to the next one */
55
+ readonly line: {
56
+ readonly left: 15;
57
+ readonly top: 32;
58
+ readonly width: 2;
59
+ readonly okShare: 0.4;
60
+ };
61
+ readonly node: {
62
+ readonly size: 32;
63
+ readonly borderWidth: 2;
64
+ readonly iconSize: 16;
65
+ };
66
+ readonly kicker: {
67
+ readonly fontSize: 11;
68
+ readonly fontWeight: 700;
69
+ readonly letterSpacing: 0.08;
70
+ readonly paddingTop: 7;
71
+ };
72
+ readonly identity: {
73
+ readonly gap: 14;
74
+ readonly avatarSize: 56;
75
+ readonly avatarRadius: 14;
76
+ readonly avatarFontSize: 18;
77
+ readonly textGap: 4;
78
+ };
79
+ readonly name: {
80
+ readonly fontSize: 20;
81
+ readonly fontWeight: 700;
82
+ readonly gap: 8;
83
+ readonly lineHeight: 1.2;
84
+ readonly iconSize: 18;
85
+ };
86
+ readonly facts: {
87
+ readonly gap: 14;
88
+ readonly columnGap: 2;
89
+ readonly fontSize: 13;
90
+ readonly linkGap: 3;
91
+ readonly linkIconSize: 11;
92
+ };
93
+ readonly provenance: {
94
+ readonly fontSize: 13;
95
+ };
96
+ readonly section: {
97
+ readonly paddingTop: 12;
98
+ readonly titleMarginBottom: 8;
99
+ readonly titleFontSize: 12;
100
+ readonly titleWeight: 700;
101
+ readonly titleSpacing: 0.08;
102
+ readonly titleGap: 8;
103
+ readonly iconSize: 16;
104
+ };
105
+ readonly count: {
106
+ readonly minWidth: 20;
107
+ readonly height: 20;
108
+ readonly paddingX: 6;
109
+ readonly radius: 10;
110
+ readonly fontSize: 11;
111
+ readonly tint: 0.14;
112
+ };
113
+ readonly row: {
114
+ readonly gap: 10;
115
+ readonly paddingY: 7;
116
+ readonly fontSize: 14;
117
+ readonly titleWeight: 600;
118
+ readonly titleGap: 6;
119
+ readonly titleIconSize: 14;
120
+ readonly linkIconSize: 12;
121
+ };
122
+ readonly valid: {
123
+ readonly gap: 4;
124
+ readonly fontSize: 13;
125
+ readonly fontWeight: 600;
126
+ readonly iconSize: 14;
127
+ };
128
+ readonly role: {
129
+ readonly fontSize: 11;
130
+ readonly fontWeight: 700;
131
+ readonly letterSpacing: 0.05;
132
+ readonly paddingY: 2;
133
+ readonly paddingX: 8;
134
+ readonly radius: 6;
135
+ readonly tint: 0.12;
136
+ readonly ecosystemTint: 0.14;
137
+ };
138
+ readonly tag: {
139
+ readonly fontSize: 11;
140
+ readonly fontWeight: 600;
141
+ readonly radius: 6;
142
+ readonly paddingY: 1;
143
+ readonly paddingX: 6;
144
+ readonly tint: 0.12;
145
+ };
146
+ readonly claims: {
147
+ readonly marginTop: 2;
148
+ readonly rowGap: 2;
149
+ readonly columnGap: 12;
150
+ readonly fontSize: 13;
151
+ };
152
+ readonly endpoint: {
153
+ readonly fontSize: 12;
154
+ };
155
+ readonly actions: {
156
+ readonly gap: 10;
157
+ readonly paddingTop: 14;
158
+ };
159
+ };
160
+ export type ProofOfTrustDesign = typeof PROOF_OF_TRUST_DESIGN;
161
+ /** Which colour role paints what on the Proof of Trust. */
162
+ export declare const PROOF_OF_TRUST_COLORS: {
163
+ readonly view: {
164
+ readonly background: "surface";
165
+ };
166
+ readonly card: {
167
+ readonly background: "bg";
168
+ readonly border: "border";
169
+ };
170
+ readonly did: {
171
+ readonly text: "muted";
172
+ };
173
+ readonly verdictSub: {
174
+ readonly text: "muted";
175
+ };
176
+ readonly reasons: {
177
+ readonly text: "danger";
178
+ };
179
+ readonly loading: {
180
+ readonly text: "muted";
181
+ };
182
+ readonly line: {
183
+ readonly ok: "ok";
184
+ readonly rest: "border";
185
+ };
186
+ readonly node: {
187
+ readonly ok: "ok";
188
+ readonly bad: "danger";
189
+ readonly background: "bg";
190
+ };
191
+ readonly kicker: {
192
+ readonly text: "muted";
193
+ };
194
+ readonly nameIcon: {
195
+ readonly color: "muted";
196
+ };
197
+ readonly facts: {
198
+ readonly text: "muted";
199
+ };
200
+ readonly provenance: {
201
+ readonly text: "muted";
202
+ };
203
+ readonly section: {
204
+ readonly border: "border";
205
+ readonly title: "muted";
206
+ };
207
+ readonly count: {
208
+ readonly background: "accent";
209
+ readonly text: "accent";
210
+ };
211
+ readonly valid: {
212
+ readonly ok: "ok";
213
+ readonly bad: "danger";
214
+ };
215
+ readonly role: {
216
+ readonly text: "accent";
217
+ readonly ecosystem: "warning";
218
+ };
219
+ readonly tag: {
220
+ readonly text: "accent";
221
+ };
222
+ readonly claims: {
223
+ readonly key: "muted";
224
+ };
225
+ };
226
+ /** The verdict the card shows: the registry's answer once loaded, the agent's last known result before. */
227
+ export declare function verdictOf(details: Pick<TrustDetails, 'trusted'> | null | undefined, fallback: TrustResult): TrustResult;
228
+ /** The icon and the colour of a verdict: those of the trust mark. */
229
+ export declare const VERDICT_MARK: {
230
+ readonly trusted: {
231
+ readonly icon: "shield-check";
232
+ readonly color: "ok";
233
+ };
234
+ readonly untrusted: {
235
+ readonly icon: "shield-alert";
236
+ readonly color: "danger";
237
+ };
238
+ readonly unverified: {
239
+ readonly icon: "shield-question";
240
+ readonly color: "warning";
241
+ };
242
+ };
243
+ /** A DID short enough for one line: its two ends. */
244
+ export declare function shortDid(did: string): string;
245
+ export declare function schemaLabel(title: string): string;
246
+ /** The DID in the Verana search, from the graph's address. */
247
+ export declare function didSearchUrl(graphUrl: string, did: string): string;
248
+ /**
249
+ * The wording of the card, in English: the browser's translation files hold the same keys (a test compares the
250
+ * English one with this), and the app, which speaks English for now, reads it from here.
251
+ */
252
+ export declare const PROOF_OF_TRUST_COPY: {
253
+ readonly title: "Proof of Trust";
254
+ readonly loading: "Checking the registry";
255
+ readonly copyDid: "Copy the DID";
256
+ readonly openInGraph: "Open in the Verana search";
257
+ readonly evaluated: "Evaluated {{time}}";
258
+ readonly block: "block {{block}}";
259
+ readonly service: "Service";
260
+ readonly operatedBy: "Operated by";
261
+ readonly selfIssued: "Self-issued";
262
+ readonly issuedBy: "Issued by {{name}}";
263
+ readonly issuedByUnknown: "Issuer accredited";
264
+ readonly since: "since {{time}}";
265
+ readonly validUntil: "valid until {{time}}";
266
+ readonly minimumAge: "Minimum age {{age}}";
267
+ readonly registryId: "Registry id";
268
+ readonly jurisdiction: "Jurisdiction";
269
+ readonly alsoPresents: "Also presents";
270
+ readonly verified: "verified";
271
+ readonly invalid: "invalid";
272
+ readonly accreditations: "Accreditations";
273
+ readonly endpoints: "Endpoints in the DID Document";
274
+ readonly governance: "Governance";
275
+ readonly ecosystem: "Ecosystem";
276
+ readonly root: "accredited by the ecosystem";
277
+ readonly egf: "Governance framework";
278
+ readonly cgf: "Corporate governance framework";
279
+ readonly corporation: "Corporation {{id}}";
280
+ readonly via: "via {{chain}}";
281
+ readonly 'role.ECOSYSTEM': "Ecosystem";
282
+ readonly 'role.ISSUER_GRANTOR': "Issuer grantor";
283
+ readonly 'role.VERIFIER_GRANTOR': "Verifier grantor";
284
+ readonly 'role.ISSUER': "Issuer";
285
+ readonly 'role.VERIFIER': "Verifier";
286
+ readonly 'role.HOLDER': "Holder";
287
+ };
288
+ export type ProofOfTrustKey = keyof typeof PROOF_OF_TRUST_COPY;
289
+ /** The wording of a verdict and of the reasons behind one that is not "trusted" (the browser's `trust.*` keys). */
290
+ export declare const TRUST_COPY: {
291
+ readonly trusted: "Trusted";
292
+ readonly untrusted: "Untrusted";
293
+ readonly unverified: "Could not verify";
294
+ readonly 'reasons.not-trusted': "The Verana registry does not vouch for this service.";
295
+ readonly 'reasons.did-not-found': "The DID is unknown to the registry.";
296
+ readonly 'reasons.resolution-unavailable': "The trust resolution service could not be reached.";
297
+ readonly 'reasons.no-service-credential': "No service credential was found.";
298
+ };
299
+ /** A text of the card with its values in place: what the renderer's translator returns. */
300
+ export type ProofOfTrustText = (key: ProofOfTrustKey, values?: Record<string, string | number>) => string;
301
+ /** Fills the `{{name}}` places of a text. */
302
+ export declare function fillText(template: string, values?: Record<string, string | number>): string;
303
+ /** The card's wording in English, for a renderer with no translation files. */
304
+ export declare const proofOfTrustEnglish: ProofOfTrustText;
305
+ /** One identity step as the card draws it: who the credential names, and what it says about them. */
306
+ export interface IdentityStepView {
307
+ kind: 'service' | 'operator';
308
+ valid: boolean;
309
+ /** "Service · ECS-Service" */
310
+ kicker: string;
311
+ name: string;
312
+ logo?: string;
313
+ /** the icon before an operator's name */
314
+ nameIcon?: 'building' | 'user';
315
+ flag?: string;
316
+ countryCode?: string;
317
+ /** a service: its type, as a tag */
318
+ tag?: string;
319
+ description?: string;
320
+ /** a service: the facts on one line; an operator: one per line */
321
+ factsLayout: 'row' | 'column';
322
+ facts: {
323
+ label?: string;
324
+ value: string;
325
+ }[];
326
+ links: {
327
+ label: 'terms' | 'privacy';
328
+ url: string;
329
+ }[];
330
+ /** "Issued by … · ecosystem · since … · valid until …" */
331
+ provenance: string;
332
+ }
333
+ /**
334
+ * An identity credential as its step shows it (UI-TRUST-003). `t` gives the wording, `day` writes a date; the
335
+ * terms and privacy links carry a label the renderer words itself (they are the column's).
336
+ */
337
+ export declare function identityStepOf(kind: 'service' | 'operator', card: CredentialCard, t: ProofOfTrustText, day: (iso: string) => string): IdentityStepView;
338
+ /** The line under another credential the DID presents: its issuer, its ecosystem, its end. */
339
+ export declare function otherCredentialLine(card: CredentialCard, t: ProofOfTrustText, when: (iso: string) => string): string;
340
+ /**
341
+ * The line of an accreditation: its ecosystem, then the grantors between the ecosystem's root and this participant
342
+ * (none means the ecosystem accredited it directly), then its state when it is not active.
343
+ */
344
+ export declare function accreditationLine(a: Accreditation, t: ProofOfTrustText): string;
345
+ /** The claims of a non-identity credential, flat key and value, strings and numbers only. */
346
+ export declare function claimRows(claims: Record<string, unknown>): {
347
+ key: string;
348
+ value: string;
349
+ link: boolean;
350
+ }[];
@@ -0,0 +1,264 @@
1
+ import { flagOf } from './flag.js';
2
+ import { TRUST_MARK } from './homeDesign.js';
3
+ export const PROOF_OF_TRUST_DESIGN = {
4
+ /** the card itself; on a phone it takes the screen, with no border and no radius */
5
+ card: { maxWidth: 760, radius: 18 },
6
+ head: {
7
+ gap: 6,
8
+ paddingY: 12,
9
+ paddingRight: 14,
10
+ paddingLeft: 18,
11
+ dotSize: 10,
12
+ didFontSize: 12,
13
+ toolIconSize: 15,
14
+ closeIconSize: 18,
15
+ },
16
+ body: { paddingTop: 18, paddingX: 22, paddingBottom: 22, gap: 18 },
17
+ verdict: {
18
+ gap: 10,
19
+ paddingY: 12,
20
+ paddingX: 16,
21
+ radius: 14,
22
+ borderWidth: 2,
23
+ iconSize: 22,
24
+ /** the share of the verdict's colour mixed into the background */
25
+ tint: 0.08,
26
+ labelFontSize: 18,
27
+ labelWeight: 700,
28
+ labelSpacing: 0.06,
29
+ subFontSize: 12,
30
+ },
31
+ reasons: { fontSize: 14, paddingLeft: 18 },
32
+ loading: { gap: 8, fontSize: 13, iconSize: 16 },
33
+ /** one identity step: its node on the line, then who it is */
34
+ step: { gap: 16, paddingBottom: 22, bodyGap: 8 },
35
+ /** the line that joins a step's node to the next one */
36
+ line: { left: 15, top: 32, width: 2, okShare: 0.4 },
37
+ node: { size: 32, borderWidth: 2, iconSize: 16 },
38
+ kicker: { fontSize: 11, fontWeight: 700, letterSpacing: 0.08, paddingTop: 7 },
39
+ identity: { gap: 14, avatarSize: 56, avatarRadius: 14, avatarFontSize: 18, textGap: 4 },
40
+ name: { fontSize: 20, fontWeight: 700, gap: 8, lineHeight: 1.2, iconSize: 18 },
41
+ facts: { gap: 14, columnGap: 2, fontSize: 13, linkGap: 3, linkIconSize: 11 },
42
+ provenance: { fontSize: 13 },
43
+ section: {
44
+ paddingTop: 12,
45
+ titleMarginBottom: 8,
46
+ titleFontSize: 12,
47
+ titleWeight: 700,
48
+ titleSpacing: 0.08,
49
+ titleGap: 8,
50
+ iconSize: 16,
51
+ },
52
+ count: { minWidth: 20, height: 20, paddingX: 6, radius: 10, fontSize: 11, tint: 0.14 },
53
+ row: { gap: 10, paddingY: 7, fontSize: 14, titleWeight: 600, titleGap: 6, titleIconSize: 14, linkIconSize: 12 },
54
+ valid: { gap: 4, fontSize: 13, fontWeight: 600, iconSize: 14 },
55
+ role: {
56
+ fontSize: 11,
57
+ fontWeight: 700,
58
+ letterSpacing: 0.05,
59
+ paddingY: 2,
60
+ paddingX: 8,
61
+ radius: 6,
62
+ tint: 0.12,
63
+ ecosystemTint: 0.14,
64
+ },
65
+ tag: { fontSize: 11, fontWeight: 600, radius: 6, paddingY: 1, paddingX: 6, tint: 0.12 },
66
+ claims: { marginTop: 2, rowGap: 2, columnGap: 12, fontSize: 13 },
67
+ endpoint: { fontSize: 12 },
68
+ actions: { gap: 10, paddingTop: 14 },
69
+ };
70
+ /** Which colour role paints what on the Proof of Trust. */
71
+ export const PROOF_OF_TRUST_COLORS = {
72
+ view: { background: 'surface' },
73
+ card: { background: 'bg', border: 'border' },
74
+ did: { text: 'muted' },
75
+ verdictSub: { text: 'muted' },
76
+ reasons: { text: 'danger' },
77
+ loading: { text: 'muted' },
78
+ line: { ok: 'ok', rest: 'border' },
79
+ node: { ok: 'ok', bad: 'danger', background: 'bg' },
80
+ kicker: { text: 'muted' },
81
+ nameIcon: { color: 'muted' },
82
+ facts: { text: 'muted' },
83
+ provenance: { text: 'muted' },
84
+ section: { border: 'border', title: 'muted' },
85
+ count: { background: 'accent', text: 'accent' },
86
+ valid: { ok: 'ok', bad: 'danger' },
87
+ role: { text: 'accent', ecosystem: 'warning' },
88
+ tag: { text: 'accent' },
89
+ claims: { key: 'muted' },
90
+ };
91
+ /** The verdict the card shows: the registry's answer once loaded, the agent's last known result before. */
92
+ export function verdictOf(details, fallback) {
93
+ if (!details)
94
+ return fallback;
95
+ return details.trusted === null ? 'unverified' : details.trusted ? 'trusted' : 'untrusted';
96
+ }
97
+ /** The icon and the colour of a verdict: those of the trust mark. */
98
+ export const VERDICT_MARK = TRUST_MARK;
99
+ /** A DID short enough for one line: its two ends. */
100
+ export function shortDid(did) {
101
+ return did.length > 48 ? `${did.slice(0, 22)}…${did.slice(-20)}` : did;
102
+ }
103
+ /** ECS schema titles as people read them on the wallet cards. */
104
+ const ECS_LABEL = {
105
+ ServiceCredential: 'ECS-Service',
106
+ OrganizationCredential: 'ECS-Organization',
107
+ PersonaCredential: 'ECS-Persona',
108
+ UserAgentCredential: 'ECS-UserAgent',
109
+ };
110
+ export function schemaLabel(title) {
111
+ return ECS_LABEL[title] ?? title;
112
+ }
113
+ /** The DID in the Verana search, from the graph's address. */
114
+ export function didSearchUrl(graphUrl, did) {
115
+ return `${graphUrl.replace(/graph\./, 'search.')}/?q=${encodeURIComponent(did)}`;
116
+ }
117
+ /**
118
+ * The wording of the card, in English: the browser's translation files hold the same keys (a test compares the
119
+ * English one with this), and the app, which speaks English for now, reads it from here.
120
+ */
121
+ export const PROOF_OF_TRUST_COPY = {
122
+ title: 'Proof of Trust',
123
+ loading: 'Checking the registry',
124
+ copyDid: 'Copy the DID',
125
+ openInGraph: 'Open in the Verana search',
126
+ evaluated: 'Evaluated {{time}}',
127
+ block: 'block {{block}}',
128
+ service: 'Service',
129
+ operatedBy: 'Operated by',
130
+ selfIssued: 'Self-issued',
131
+ issuedBy: 'Issued by {{name}}',
132
+ issuedByUnknown: 'Issuer accredited',
133
+ since: 'since {{time}}',
134
+ validUntil: 'valid until {{time}}',
135
+ minimumAge: 'Minimum age {{age}}',
136
+ registryId: 'Registry id',
137
+ jurisdiction: 'Jurisdiction',
138
+ alsoPresents: 'Also presents',
139
+ verified: 'verified',
140
+ invalid: 'invalid',
141
+ accreditations: 'Accreditations',
142
+ endpoints: 'Endpoints in the DID Document',
143
+ governance: 'Governance',
144
+ ecosystem: 'Ecosystem',
145
+ root: 'accredited by the ecosystem',
146
+ egf: 'Governance framework',
147
+ cgf: 'Corporate governance framework',
148
+ corporation: 'Corporation {{id}}',
149
+ via: 'via {{chain}}',
150
+ 'role.ECOSYSTEM': 'Ecosystem',
151
+ 'role.ISSUER_GRANTOR': 'Issuer grantor',
152
+ 'role.VERIFIER_GRANTOR': 'Verifier grantor',
153
+ 'role.ISSUER': 'Issuer',
154
+ 'role.VERIFIER': 'Verifier',
155
+ 'role.HOLDER': 'Holder',
156
+ };
157
+ /** The wording of a verdict and of the reasons behind one that is not "trusted" (the browser's `trust.*` keys). */
158
+ export const TRUST_COPY = {
159
+ trusted: 'Trusted',
160
+ untrusted: 'Untrusted',
161
+ unverified: 'Could not verify',
162
+ 'reasons.not-trusted': 'The Verana registry does not vouch for this service.',
163
+ 'reasons.did-not-found': 'The DID is unknown to the registry.',
164
+ 'reasons.resolution-unavailable': 'The trust resolution service could not be reached.',
165
+ 'reasons.no-service-credential': 'No service credential was found.',
166
+ };
167
+ /** Fills the `{{name}}` places of a text. */
168
+ export function fillText(template, values = {}) {
169
+ return template.replace(/\{\{(\w+)\}\}/g, (_, name) => String(values[name] ?? ''));
170
+ }
171
+ /** The card's wording in English, for a renderer with no translation files. */
172
+ export const proofOfTrustEnglish = (key, values) => fillText(PROOF_OF_TRUST_COPY[key], values);
173
+ const text = (v) => (typeof v === 'string' && v ? v : undefined);
174
+ /**
175
+ * An identity credential as its step shows it (UI-TRUST-003). `t` gives the wording, `day` writes a date; the
176
+ * terms and privacy links carry a label the renderer words itself (they are the column's).
177
+ */
178
+ export function identityStepOf(kind, card, t, day) {
179
+ const c = card.claims;
180
+ const facts = [];
181
+ const links = [];
182
+ if (kind === 'service') {
183
+ if (typeof c.minimumAgeRequired === 'number' && c.minimumAgeRequired > 0)
184
+ facts.push({ value: t('minimumAge', { age: c.minimumAgeRequired }) });
185
+ const terms = text(c.termsAndConditionsUri);
186
+ const privacy = text(c.privacyPolicyUri);
187
+ if (terms)
188
+ links.push({ label: 'terms', url: terms });
189
+ if (privacy)
190
+ links.push({ label: 'privacy', url: privacy });
191
+ }
192
+ else {
193
+ const address = text(c.address);
194
+ if (address)
195
+ facts.push({ value: address });
196
+ if (c.registryId)
197
+ facts.push({ label: t('registryId'), value: String(c.registryId) });
198
+ if (c.lei)
199
+ facts.push({ label: 'LEI', value: String(c.lei) });
200
+ if (c.legalJurisdiction)
201
+ facts.push({ label: t('jurisdiction'), value: String(c.legalJurisdiction) });
202
+ }
203
+ const provenance = [
204
+ card.selfIssued
205
+ ? t('selfIssued')
206
+ : card.issuer.name
207
+ ? t('issuedBy', { name: card.issuer.name })
208
+ : t('issuedByUnknown'),
209
+ card.ecosystem.name,
210
+ card.issuedAt ? t('since', { time: day(card.issuedAt) }) : undefined,
211
+ card.validUntil ? t('validUntil', { time: day(card.validUntil) }) : undefined,
212
+ ]
213
+ .filter(Boolean)
214
+ .join(' · ');
215
+ const countryCode = text(c.countryCode);
216
+ return {
217
+ kind,
218
+ valid: card.valid,
219
+ kicker: `${t(kind === 'service' ? 'service' : 'operatedBy')} · ${schemaLabel(card.schemaTitle)}`,
220
+ name: String(c.name ?? ''),
221
+ logo: text(c.logoUri) ?? text(c.avatarUri),
222
+ nameIcon: kind === 'operator' ? (card.ecsSchema === 'PersonaCredential' ? 'user' : 'building') : undefined,
223
+ flag: flagOf(countryCode) || undefined,
224
+ countryCode,
225
+ tag: kind === 'service' ? text(c.type) : undefined,
226
+ description: kind === 'service' ? text(c.description) : undefined,
227
+ factsLayout: kind === 'service' ? 'row' : 'column',
228
+ facts,
229
+ links,
230
+ provenance,
231
+ };
232
+ }
233
+ /** The line under another credential the DID presents: its issuer, its ecosystem, its end. */
234
+ export function otherCredentialLine(card, t, when) {
235
+ return [
236
+ card.issuer.name ? t('issuedBy', { name: card.issuer.name }) : undefined,
237
+ card.ecosystem.name,
238
+ card.validUntil ? t('validUntil', { time: when(card.validUntil) }) : undefined,
239
+ ]
240
+ .filter(Boolean)
241
+ .join(' · ');
242
+ }
243
+ /**
244
+ * The line of an accreditation: its ecosystem, then the grantors between the ecosystem's root and this participant
245
+ * (none means the ecosystem accredited it directly), then its state when it is not active.
246
+ */
247
+ export function accreditationLine(a, t) {
248
+ const grantors = a.chain.filter((c) => c.participantId !== a.participantId && c.role !== 'ECOSYSTEM');
249
+ return [
250
+ a.ecosystem.name ?? `${t('ecosystem')} ${a.ecosystem.id}`,
251
+ grantors.length
252
+ ? t('via', { chain: grantors.map((c) => c.who.name ?? t(`role.${c.role}`)).join(' → ') })
253
+ : t('root'),
254
+ a.state !== 'ACTIVE' ? a.state.toLowerCase() : undefined,
255
+ ]
256
+ .filter(Boolean)
257
+ .join(' · ');
258
+ }
259
+ /** The claims of a non-identity credential, flat key and value, strings and numbers only. */
260
+ export function claimRows(claims) {
261
+ return Object.entries(claims)
262
+ .filter(([k, v]) => k !== 'id' && !/Sri$/.test(k) && (typeof v === 'string' || typeof v === 'number'))
263
+ .map(([key, v]) => ({ key, value: String(v), link: /^https?:\/\//.test(String(v)) }));
264
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayni.org/ayni-ui-core",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Framework-independent core of the Ayni UI (UI-SHELL-004): agents and controllers, trust resolution, sessions and binding, the conversation reducer, the local store and sync; shared by the browser UI and the Ayni app",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",