@estiva-app/interop 0.1.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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/projection.d.ts +524 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +1334 -0
- package/dist/projection.js.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +46 -0
- package/src/projection.ts +1833 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering another app's objects from its published manifest — the pure part.
|
|
3
|
+
*
|
|
4
|
+
* Split from `foreign.ts` so it can be exercised against a live relay without a
|
|
5
|
+
* Convex deployment: everything here takes a `query` function rather than
|
|
6
|
+
* reaching for one. `foreign.ts` supplies the authenticated bridge; a test
|
|
7
|
+
* supplies its own.
|
|
8
|
+
*
|
|
9
|
+
* **Nothing in this file knows what Linear-lite is.** No kind number, no field
|
|
10
|
+
* name, no status vocabulary is hardcoded — every one comes off the NIP-89
|
|
11
|
+
* manifest at runtime. If that stops being true the demo stops proving
|
|
12
|
+
* anything: it becomes an integration written against one app, which is the
|
|
13
|
+
* thing the whole exercise argues against.
|
|
14
|
+
*/
|
|
15
|
+
import { type AddressPointer } from '@estiva-app/protocol';
|
|
16
|
+
import { type Profile, type SignedEvent } from '@estiva-app/protocol';
|
|
17
|
+
/** Query the relay. Returns matching events; shape mirrors the HTTP bridge. */
|
|
18
|
+
export type QueryFn = (filters: Record<string, unknown>[]) => Promise<SignedEvent[]>;
|
|
19
|
+
/** An unsigned Nostr event, ready for `sign.ts`. */
|
|
20
|
+
export interface UnsignedActionEvent {
|
|
21
|
+
pubkey: string;
|
|
22
|
+
created_at: number;
|
|
23
|
+
kind: number;
|
|
24
|
+
tags: string[][];
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The people named on another app's objects (FEE-1).
|
|
29
|
+
*
|
|
30
|
+
* A manifest says a slot holds a pubkey; it never says whose. Peek showed the
|
|
31
|
+
* first eight characters of the key, which is not a person — it is the thing a
|
|
32
|
+
* person is behind. The name and face come from kind:0, the same place the
|
|
33
|
+
* message list already gets them, so the same person reads the same way in both.
|
|
34
|
+
*
|
|
35
|
+
* Keyed by pubkey. A key that resolves to nothing is simply absent, and the
|
|
36
|
+
* renderer decides what an unknown person looks like.
|
|
37
|
+
*/
|
|
38
|
+
export type People = Record<string, Profile>;
|
|
39
|
+
/**
|
|
40
|
+
* Looking people up, as a seam rather than a direct call.
|
|
41
|
+
*
|
|
42
|
+
* The browser puts a session cache in front of this: a topic with ten reference
|
|
43
|
+
* widgets resolves ten times, and it is the same handful of people every time.
|
|
44
|
+
* Tests and Storybook pass a fixture instead and never touch a relay.
|
|
45
|
+
*/
|
|
46
|
+
export type PeopleFn = (pubkeys: string[]) => Promise<People>;
|
|
47
|
+
/** The default: one kind:0 query, straight to the relay. */
|
|
48
|
+
export declare function peopleViaRelay(query: QueryFn): PeopleFn;
|
|
49
|
+
/**
|
|
50
|
+
* Every kind an app's comments might be under — what it publishes **now**, plus
|
|
51
|
+
* any it has published before.
|
|
52
|
+
*
|
|
53
|
+
* `emits.kind` is a single number, and for a while that was enough. It stops
|
|
54
|
+
* being enough the moment an app *changes* the kind it emits, because the old
|
|
55
|
+
* events do not move: a `kind:9` message is not replaceable at all, so a comment
|
|
56
|
+
* written under the old kind stays under it permanently. Reading only the
|
|
57
|
+
* declared kind would show an object's newest comments and silently drop every
|
|
58
|
+
* one written before the change — no error, nothing empty, just a thread that
|
|
59
|
+
* begins in the middle.
|
|
60
|
+
*
|
|
61
|
+
* So the owning app may also declare `emits.alsoRead`, which is the kinds it
|
|
62
|
+
* used to publish. That is the only place the knowledge actually lives; the
|
|
63
|
+
* alternative is Peek hardcoding one app's history, which is exactly what the
|
|
64
|
+
* note on `KIND_COMMENT` above says not to do.
|
|
65
|
+
*
|
|
66
|
+
* Absent `alsoRead` this returns a single kind, so a manifest written before
|
|
67
|
+
* the field existed behaves exactly as it did.
|
|
68
|
+
*/
|
|
69
|
+
export declare function commentKindsOf(manifest: Manifest): number[];
|
|
70
|
+
/** How the owning app says its records should be read. */
|
|
71
|
+
interface RecordsRule {
|
|
72
|
+
changeKind: number;
|
|
73
|
+
targetTag: string;
|
|
74
|
+
fieldTag: string;
|
|
75
|
+
valueTag: string;
|
|
76
|
+
order: string[];
|
|
77
|
+
rule: string;
|
|
78
|
+
/**
|
|
79
|
+
* A folded field whose value means "do not show this object".
|
|
80
|
+
*
|
|
81
|
+
* Archiving is a field, not a deletion, so an archived record still comes
|
|
82
|
+
* back from every query and renders perfectly well. Without this a consumer
|
|
83
|
+
* cannot tell it apart from live work — which is how a topic whose Folder
|
|
84
|
+
* held three archived projects ended up showing one of them.
|
|
85
|
+
*
|
|
86
|
+
* Peek honours the rule without knowing what the word means: the owner says
|
|
87
|
+
* which field and which value, and this stays out of it.
|
|
88
|
+
*/
|
|
89
|
+
hiddenWhen?: {
|
|
90
|
+
field: string;
|
|
91
|
+
equals: string;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
interface SlotSpec {
|
|
95
|
+
/**
|
|
96
|
+
* A tag on the root event, or a list of them meaning **first that resolves**.
|
|
97
|
+
*
|
|
98
|
+
* A list exists for the same reason `emits.alsoRead` does: an app that
|
|
99
|
+
* renames a tag cannot rename it on the events it already published, so its
|
|
100
|
+
* history spans both spellings permanently. Ship moving a project's title
|
|
101
|
+
* from `title` to `name` is the case in hand — without a fallback, either
|
|
102
|
+
* every record written before the rename renders blank or every one written
|
|
103
|
+
* after does.
|
|
104
|
+
*
|
|
105
|
+
* Not to be confused with a `SlotSpec[]`, which the caller treats as "render
|
|
106
|
+
* all of these as meta". This is one slot with several places to look.
|
|
107
|
+
*/
|
|
108
|
+
tag?: string | string[];
|
|
109
|
+
field?: string;
|
|
110
|
+
fold?: string;
|
|
111
|
+
map?: string;
|
|
112
|
+
as?: string;
|
|
113
|
+
label?: string;
|
|
114
|
+
truncate?: number;
|
|
115
|
+
default?: string;
|
|
116
|
+
/**
|
|
117
|
+
* **Child objects, found by the tag on the child that names this one.**
|
|
118
|
+
*
|
|
119
|
+
* The `list` slot (RFC 0.4 §13.3, PRO-2). The only slot source that does not
|
|
120
|
+
* read the root event: every other field here answers "what does this event
|
|
121
|
+
* say?", and this one answers "what points at it?".
|
|
122
|
+
*
|
|
123
|
+
* `limit` is the producer saying how many are worth fetching. **Recursion
|
|
124
|
+
* depth is deliberately not here** — a child rendered through its own
|
|
125
|
+
* projection may declare a `list` too, and the app at risk of the render loop
|
|
126
|
+
* is the one drawing it, so the budget is the consumer's (`MAX_LIST_DEPTH`).
|
|
127
|
+
* §13.4's honour-system rule cuts that way: a producer that could set the
|
|
128
|
+
* consumer's recursion budget could hang it.
|
|
129
|
+
*/
|
|
130
|
+
children?: {
|
|
131
|
+
kind: number;
|
|
132
|
+
via: string;
|
|
133
|
+
limit?: number;
|
|
134
|
+
/**
|
|
135
|
+
* What the child's `via` tag holds — added by PRO-6.
|
|
136
|
+
*
|
|
137
|
+
* `address` (the default, and Ship's case) means the tag carries the
|
|
138
|
+
* parent's full `kind:pubkey:d`. `identifier` means it carries only the
|
|
139
|
+
* parent's `d`.
|
|
140
|
+
*
|
|
141
|
+
* Found by declaring a projection for a Peek Topic. A message names its
|
|
142
|
+
* channel with `h`, and `h` holds the **bare channel uuid** — which is the
|
|
143
|
+
* topic's `d`, not its address. Nothing in NIP-29 is going to change that,
|
|
144
|
+
* so a `list` slot that could only compare addresses could not express the
|
|
145
|
+
* one relationship Peek has. Defaulting to `address` keeps every manifest
|
|
146
|
+
* written before this reading exactly as it did.
|
|
147
|
+
*/
|
|
148
|
+
match?: 'address' | 'identifier';
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** An action the owning app says other apps may perform. */
|
|
152
|
+
export interface ManifestAction {
|
|
153
|
+
id: string;
|
|
154
|
+
label: string;
|
|
155
|
+
/** Kind(s) this applies to, as strings. */
|
|
156
|
+
appliesTo: string | string[];
|
|
157
|
+
emits: {
|
|
158
|
+
kind: number;
|
|
159
|
+
field?: string;
|
|
160
|
+
scope?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Set by an action that creates a whole new object under this one: the tag
|
|
163
|
+
* the child carries, and what it points at. `toAddressOf: "self"` means the
|
|
164
|
+
* child names the object the action was invoked on.
|
|
165
|
+
*
|
|
166
|
+
* Peek reads these two to learn a **containment relation** — "kind X holds
|
|
167
|
+
* kind Y" — without being told which app or which kinds are involved. It is
|
|
168
|
+
* the only thing in the manifest that says so, which is what lets the
|
|
169
|
+
* sidebar start from a Folder and find a project with issues in it.
|
|
170
|
+
*/
|
|
171
|
+
setTag?: string;
|
|
172
|
+
toAddressOf?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Kinds this action *used* to emit, which consumers must still read.
|
|
175
|
+
*
|
|
176
|
+
* Only ever additive to a read, never to a write: `kind` is what gets
|
|
177
|
+
* published, this is what also gets fetched. See `commentKindsOf`.
|
|
178
|
+
*/
|
|
179
|
+
alsoRead?: number[];
|
|
180
|
+
};
|
|
181
|
+
input?: {
|
|
182
|
+
type: string;
|
|
183
|
+
enum?: string;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* An action resolved into something a renderer can draw without re-reading the
|
|
188
|
+
* manifest.
|
|
189
|
+
*
|
|
190
|
+
* The interpretation happens here rather than in the component on purpose: the
|
|
191
|
+
* widget should not have to know that `input.enum` names a vocabulary, or that
|
|
192
|
+
* `as: "pubkey"` means "a person". Those are manifest semantics, and keeping
|
|
193
|
+
* them on this side is what lets the React component stay a dumb renderer that
|
|
194
|
+
* would work for any app.
|
|
195
|
+
*/
|
|
196
|
+
export interface ResolvedAction {
|
|
197
|
+
id: string;
|
|
198
|
+
label: string;
|
|
199
|
+
control: 'select' | 'pubkey' | 'text';
|
|
200
|
+
/** For `select`: the declared vocabulary, already looked up. */
|
|
201
|
+
options?: {
|
|
202
|
+
value: string;
|
|
203
|
+
label: string;
|
|
204
|
+
colour?: string;
|
|
205
|
+
}[];
|
|
206
|
+
/** The value this field holds right now, so a control can show it. */
|
|
207
|
+
current?: string;
|
|
208
|
+
field?: string;
|
|
209
|
+
}
|
|
210
|
+
interface Manifest {
|
|
211
|
+
name?: string;
|
|
212
|
+
about?: string;
|
|
213
|
+
actions?: ManifestAction[];
|
|
214
|
+
records?: RecordsRule;
|
|
215
|
+
projections?: Record<string, {
|
|
216
|
+
widget: string | string[];
|
|
217
|
+
slots: Record<string, SlotSpec | SlotSpec[]>;
|
|
218
|
+
}>;
|
|
219
|
+
vocabularies?: Record<string, {
|
|
220
|
+
value: string;
|
|
221
|
+
label: string;
|
|
222
|
+
colour: string;
|
|
223
|
+
stage?: string;
|
|
224
|
+
}[]>;
|
|
225
|
+
}
|
|
226
|
+
export declare function resolveManifest(pointer: AddressPointer, query: QueryFn): Promise<{
|
|
227
|
+
manifest: Manifest;
|
|
228
|
+
address: string;
|
|
229
|
+
viaRecommendation: boolean;
|
|
230
|
+
/** NIP-89 `web` template, `<bech32>` not yet substituted. */
|
|
231
|
+
webTemplate?: string;
|
|
232
|
+
} | null>;
|
|
233
|
+
/**
|
|
234
|
+
* Which layout to draw, from a declared type or an ordered chain of them.
|
|
235
|
+
*
|
|
236
|
+
* RFC 0.4 §13.3. A widget is a layout *hint* rather than a semantic, so an
|
|
237
|
+
* unknown one can degrade honestly: `["message", "card"]` means *render me as a
|
|
238
|
+
* message if you know it, else as a card*, and a chain must terminate in a type
|
|
239
|
+
* the spec closes.
|
|
240
|
+
*
|
|
241
|
+
* **It lives here rather than in each consumer** because two implementations
|
|
242
|
+
* would disagree the first time a chain had three entries, and the whole point
|
|
243
|
+
* of the chain is that producers and consumers upgrade at different times. The
|
|
244
|
+
* consumer supplies what it implements; the runtime does the walking.
|
|
245
|
+
*
|
|
246
|
+
* `fallback` is what to draw when the chain runs out — never "nothing". §13.3's
|
|
247
|
+
* argument is that a blank object is indistinguishable from one the reader may
|
|
248
|
+
* not be allowed to see, and reports "that app is broken" about an app doing
|
|
249
|
+
* exactly what it was told.
|
|
250
|
+
*/
|
|
251
|
+
/**
|
|
252
|
+
* The widget types RFC 0.4 §13.3 closes. A chain MUST end in one of these.
|
|
253
|
+
*
|
|
254
|
+
* Exported because both halves need the same list and they must not drift: a
|
|
255
|
+
* producer checks its chain terminates here, a consumer's fallback is drawn
|
|
256
|
+
* from here. Two copies would disagree the first time the set grew, and the
|
|
257
|
+
* disagreement would show up as an object rendering blank in one app only.
|
|
258
|
+
*/
|
|
259
|
+
export declare const CLOSED_WIDGETS: readonly ["card", "row", "table", "stat"];
|
|
260
|
+
/**
|
|
261
|
+
* Why a widget declaration is not publishable, or null when it is — PRO-3.
|
|
262
|
+
*
|
|
263
|
+
* **The producer half of the fallback chain.** `pickWidget` below makes a
|
|
264
|
+
* consumer safe against a chain it does not fully understand; this stops the
|
|
265
|
+
* unrenderable chain being published in the first place. Both are needed and
|
|
266
|
+
* they fail differently: without the consumer half an unknown widget renders
|
|
267
|
+
* blank, and without this one a *conformant* consumer renders blank through no
|
|
268
|
+
* fault of its own, having done exactly what it was told.
|
|
269
|
+
*
|
|
270
|
+
* A chain that does not terminate in a closed type is the PEE-10 failure with
|
|
271
|
+
* a longer fuse — every consumer that has not heard of `profile` walks
|
|
272
|
+
* `["profile"]` to the end and has nothing left to draw.
|
|
273
|
+
*
|
|
274
|
+
* Returns a sentence rather than a boolean because this is read by a person
|
|
275
|
+
* publishing a manifest, and "invalid widget" tells them nothing about which
|
|
276
|
+
* one or what to do.
|
|
277
|
+
*/
|
|
278
|
+
export declare function widgetChainProblem(declared: unknown): string | null;
|
|
279
|
+
export declare function pickWidget<T extends string>(declared: string | string[], implemented: readonly T[], fallback: T): T;
|
|
280
|
+
/** A slot, resolved to something a renderer can put on screen. */
|
|
281
|
+
export interface ResolvedSlot {
|
|
282
|
+
label?: string;
|
|
283
|
+
value: string;
|
|
284
|
+
/** Set when the value came from a vocabulary — the consumer picks the colour. */
|
|
285
|
+
colour?: string;
|
|
286
|
+
/** True when the value is a pubkey and should be shown as a person. */
|
|
287
|
+
isPubkey?: boolean;
|
|
288
|
+
/**
|
|
289
|
+
* The underlying field this slot reads, when it has a name.
|
|
290
|
+
*
|
|
291
|
+
* Carried so a renderer can tell that a slot and an action are two views of
|
|
292
|
+
* *one* value — a status label beside a status picker is the same fact twice
|
|
293
|
+
* (PEEK-18). Matching on the field is what makes that check work for any app:
|
|
294
|
+
* the slot key (`status`) and the action's label ("Change status") are both
|
|
295
|
+
* free-form, but a `ResolvedAction.field` and this always name the same thing
|
|
296
|
+
* because the manifest wrote them both.
|
|
297
|
+
*
|
|
298
|
+
* Undefined for a slot with no named source — `{field: "content"}` reads the
|
|
299
|
+
* event body, which no action can set.
|
|
300
|
+
*/
|
|
301
|
+
field?: string;
|
|
302
|
+
}
|
|
303
|
+
/** Everything the frontend needs to draw the widget. */
|
|
304
|
+
export interface ForeignObject {
|
|
305
|
+
/**
|
|
306
|
+
* A stable unique handle for this object, whatever kind of thing it is.
|
|
307
|
+
*
|
|
308
|
+
* **Added by PRO-7, and it exists because not every object has an address.**
|
|
309
|
+
* A replaceable record is identified by `kind:pubkey:d`; a regular event —
|
|
310
|
+
* Peek's `kind:9` message is the case in hand — has no `d` at all and is
|
|
311
|
+
* identified only by its event id. Before this, `address` was required and
|
|
312
|
+
* doubled as the React key, the error-map key and the `data-` attribute, so
|
|
313
|
+
* a non-addressable object could not be represented at all.
|
|
314
|
+
*
|
|
315
|
+
* Use this for identity. Use `naddr` only where an *address* is genuinely
|
|
316
|
+
* required, which in practice means acting on an object.
|
|
317
|
+
*/
|
|
318
|
+
ref: string;
|
|
319
|
+
/**
|
|
320
|
+
* The address, `kind:pubkey:d` — **only for an addressable object.**
|
|
321
|
+
*
|
|
322
|
+
* Undefined for a regular event. See `ref`.
|
|
323
|
+
*/
|
|
324
|
+
address?: string;
|
|
325
|
+
/**
|
|
326
|
+
* The address as `naddr1…`, **only for an addressable object.**
|
|
327
|
+
*
|
|
328
|
+
* Carried alongside `address` because acting on an object is addressed by
|
|
329
|
+
* naddr (`act.ts`), and the sidebar builds its objects rather than being
|
|
330
|
+
* handed a reference somebody pasted. Without it every control in a
|
|
331
|
+
* sidebar card would have to re-encode what the server already knows.
|
|
332
|
+
*
|
|
333
|
+
* Its absence is meaningful rather than a gap: an action emits a change
|
|
334
|
+
* carrying an `a` tag, and there is nothing for that tag to point at on a
|
|
335
|
+
* non-addressable object. A consumer that has no `naddr` correctly offers no
|
|
336
|
+
* actions.
|
|
337
|
+
*/
|
|
338
|
+
naddr?: string;
|
|
339
|
+
/** The event id — set for every object, and the only handle a regular event has. */
|
|
340
|
+
eventId: string;
|
|
341
|
+
/**
|
|
342
|
+
* Child objects from a `list` slot, each resolved through its own projection.
|
|
343
|
+
*
|
|
344
|
+
* Empty rather than absent when the slot is declared and nothing matched, so
|
|
345
|
+
* a renderer can tell "this holds nothing" from "this holds no list".
|
|
346
|
+
*/
|
|
347
|
+
children?: ForeignObject[];
|
|
348
|
+
kind: number;
|
|
349
|
+
/**
|
|
350
|
+
* The layout hint the owner declared — **a type or an ordered chain of them.**
|
|
351
|
+
*
|
|
352
|
+
* `["message", "card"]` means *render me as a message if you know it, else as
|
|
353
|
+
* a card*, and a chain MUST terminate in a type RFC 0.4 §13.3 closes
|
|
354
|
+
* (`card`/`row`/`table`/`stat`). A bare string is the older form and is a
|
|
355
|
+
* chain of one.
|
|
356
|
+
*
|
|
357
|
+
* **This was typed `string` until PRO-7**, while Peek's own published manifest
|
|
358
|
+
* declares `["message","card"]` — so the type said one thing and the wire said
|
|
359
|
+
* another, and a consumer writing `widget === 'card'` compared a string to an
|
|
360
|
+
* array and silently drew nothing. Found by Ship, the second consumer, the
|
|
361
|
+
* first time anything typechecked a renderer against a real chain. That is
|
|
362
|
+
* the answer to PRO-1's "what did the second consumer force to change".
|
|
363
|
+
*/
|
|
364
|
+
widget: string | string[];
|
|
365
|
+
appName?: string;
|
|
366
|
+
/** Named single slots: title, subtitle, status. */
|
|
367
|
+
slots: Record<string, ResolvedSlot>;
|
|
368
|
+
/** Repeating slots, e.g. `meta`. */
|
|
369
|
+
meta: ResolvedSlot[];
|
|
370
|
+
comments: {
|
|
371
|
+
id: string;
|
|
372
|
+
author: string;
|
|
373
|
+
body: string;
|
|
374
|
+
createdAt: number;
|
|
375
|
+
}[];
|
|
376
|
+
/**
|
|
377
|
+
* The Folder this object lives in, from the root event's `h` tag.
|
|
378
|
+
*
|
|
379
|
+
* Needed to *write*: a change event carries the same `h`, and without it the
|
|
380
|
+
* relay rejects the write outright. Reading it off the object rather than
|
|
381
|
+
* asking the user is the difference between an action that works and a form
|
|
382
|
+
* with a "Folder id" box in it.
|
|
383
|
+
*/
|
|
384
|
+
folder?: string;
|
|
385
|
+
/**
|
|
386
|
+
* Where to open this object in the app that owns it, from NIP-89's `web` tag.
|
|
387
|
+
*
|
|
388
|
+
* The return leg of the roundtrip. Without it a reference is a read-only
|
|
389
|
+
* snapshot: Peek can render a Linear-lite issue and change its status, and
|
|
390
|
+
* getting back to the issue itself means alt-tabbing and hunting for a row.
|
|
391
|
+
*
|
|
392
|
+
* Absent when the owning app published no template — the widget simply does
|
|
393
|
+
* not offer the link, rather than guessing a URL.
|
|
394
|
+
*/
|
|
395
|
+
openUrl?: string;
|
|
396
|
+
/** What the owning app says we may do to this object. */
|
|
397
|
+
actions: ResolvedAction[];
|
|
398
|
+
/**
|
|
399
|
+
* Names and faces for every pubkey this object shows (FEE-1).
|
|
400
|
+
*
|
|
401
|
+
* Carried on the object rather than looked up by the renderer so a widget is
|
|
402
|
+
* still a pure render of what it was handed — the same property that lets
|
|
403
|
+
* Storybook draw a real person from a fixture. Empty when nothing published a
|
|
404
|
+
* profile, which the renderer treats as "someone we cannot name" rather than
|
|
405
|
+
* falling back to the key.
|
|
406
|
+
*/
|
|
407
|
+
people?: People;
|
|
408
|
+
/** False when no author recommendation existed and a handler was guessed. */
|
|
409
|
+
viaRecommendation: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Set when the manifest resolved but the object itself did not.
|
|
412
|
+
*
|
|
413
|
+
* Almost always a permission problem rather than a missing object, and worth
|
|
414
|
+
* distinguishing because the relay makes them look identical. Objects and
|
|
415
|
+
* change events are channel-scoped, so a reader who is not a member of the
|
|
416
|
+
* Folder gets zero rows — no error, just silence. The manifest and the
|
|
417
|
+
* recommendation are global kinds and resolve fine without membership, so the
|
|
418
|
+
* failure lands late and presents as a broken reference. Telling the user
|
|
419
|
+
* "you may not have access to this Folder" is a far better guess than
|
|
420
|
+
* rendering nothing.
|
|
421
|
+
*/
|
|
422
|
+
unreachable?: boolean;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Resolve a `nostr:naddr…` into a renderable widget.
|
|
426
|
+
*
|
|
427
|
+
* Returns `null` rather than throwing when the object cannot be rendered — an
|
|
428
|
+
* unresolvable reference in a chat message should degrade to plain text, not
|
|
429
|
+
* break the message around it.
|
|
430
|
+
*/
|
|
431
|
+
export declare function resolveForeignObject(naddr: string, query: QueryFn,
|
|
432
|
+
/** Defaults to asking the relay. The browser passes a cached lookup. */
|
|
433
|
+
lookupPeople?: PeopleFn,
|
|
434
|
+
/**
|
|
435
|
+
* How many `list` levels have already been followed. Callers outside this
|
|
436
|
+
* module leave it at 0; it is the budget that stops a child's own `list`
|
|
437
|
+
* recursing forever. See `MAX_LIST_DEPTH`.
|
|
438
|
+
*/
|
|
439
|
+
depth?: number): Promise<ForeignObject | null>;
|
|
440
|
+
/**
|
|
441
|
+
* A Folder's project and the tickets in motion in it.
|
|
442
|
+
*
|
|
443
|
+
* Both are ordinary `ForeignObject`s — the same shape an `naddr` in a message
|
|
444
|
+
* resolves to, carrying the same slots, the same declared actions and the same
|
|
445
|
+
* link back into the owning app. The sidebar therefore renders the owning app's
|
|
446
|
+
* objects with the owning app's affordances rather than a reduced copy of them,
|
|
447
|
+
* and a ticket is as actionable there as it is inline in a conversation.
|
|
448
|
+
*/
|
|
449
|
+
export interface FolderProject {
|
|
450
|
+
project: ForeignObject;
|
|
451
|
+
/**
|
|
452
|
+
* Every ticket in the project, finished or not: started work first, then the
|
|
453
|
+
* queue, then what is done. Archived ones are left out — an app hiding a
|
|
454
|
+
* record from its own lists is saying it is no longer part of the project.
|
|
455
|
+
*/
|
|
456
|
+
tickets: ForeignObject[];
|
|
457
|
+
/** Tickets still to do. With `doneCount`, the total the panel counts against. */
|
|
458
|
+
openCount: number;
|
|
459
|
+
/** Finished tickets — the numerator in the panel's "1/3". */
|
|
460
|
+
doneCount: number;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* The project living in a Folder, plus the work currently in motion in it.
|
|
464
|
+
*
|
|
465
|
+
* Starts from a **Folder** rather than an naddr, which is the difference
|
|
466
|
+
* between this and `resolveForeignObject`: a Peek topic and a Linear-lite
|
|
467
|
+
* project are one container (RFC_UPDATES.md §1.1), so a topic already knows
|
|
468
|
+
* enough to ask "what project is this?" without anyone pasting a reference.
|
|
469
|
+
*
|
|
470
|
+
* Still knows nothing about Linear-lite. Which kind is a project, which kind is
|
|
471
|
+
* an issue, how they link, how status folds and what it is called all come off
|
|
472
|
+
* published manifests at runtime — the same discipline as the rest of this file.
|
|
473
|
+
*
|
|
474
|
+
* Returns null for "nothing to show": no manifest declares a container, no
|
|
475
|
+
* project in this Folder and none named by the tickets in it, or the object
|
|
476
|
+
* cannot be read. All of those are ordinary states the caller renders as an
|
|
477
|
+
* empty sidebar, not errors.
|
|
478
|
+
*/
|
|
479
|
+
export declare function resolveFolderProject(folder: string, query: QueryFn,
|
|
480
|
+
/** Defaults to asking the relay. The browser passes a cached lookup. */
|
|
481
|
+
lookupPeople?: PeopleFn,
|
|
482
|
+
/**
|
|
483
|
+
* How many `list` levels have already been followed to get here. Callers
|
|
484
|
+
* outside this module leave it at 0; it exists so that following a child's
|
|
485
|
+
* own `list` is a budget check rather than a thing nobody remembered.
|
|
486
|
+
*/
|
|
487
|
+
depth?: number): Promise<FolderProject | null>;
|
|
488
|
+
/**
|
|
489
|
+
* Build the event that performs a manifest-declared action.
|
|
490
|
+
*
|
|
491
|
+
* Pure, and separate from the Convex action for the same reason `projection.ts`
|
|
492
|
+
* is separate from `foreign.ts`: this is the part worth testing against a live
|
|
493
|
+
* relay, and it must not need a deployment or a signed-in session to run.
|
|
494
|
+
*
|
|
495
|
+
* Returns a string on refusal rather than throwing — every failure here is
|
|
496
|
+
* something a user should read.
|
|
497
|
+
*/
|
|
498
|
+
export declare function buildActionEvent(args: {
|
|
499
|
+
manifest: {
|
|
500
|
+
records?: RecordsRule;
|
|
501
|
+
actions?: ManifestAction[];
|
|
502
|
+
vocabularies?: Manifest['vocabularies'];
|
|
503
|
+
};
|
|
504
|
+
kind: number;
|
|
505
|
+
/** Address of the object being acted on. */
|
|
506
|
+
address: string;
|
|
507
|
+
/** Author of the object — needed for NIP-22's `P`/`p` tags. */
|
|
508
|
+
objectAuthor: string;
|
|
509
|
+
folder: string;
|
|
510
|
+
actionId: string;
|
|
511
|
+
value: string;
|
|
512
|
+
pubkey: string;
|
|
513
|
+
createdAtMs: number;
|
|
514
|
+
}): UnsignedActionEvent | string;
|
|
515
|
+
/**
|
|
516
|
+
* Test seam: resolve one projection's `title` slot against one event.
|
|
517
|
+
*
|
|
518
|
+
* Exported so the tag-fallback rules can be pinned without standing up a fake
|
|
519
|
+
* relay. `resolveSlots` and `resolveSlot` are the real path; this only picks
|
|
520
|
+
* the one slot out of them.
|
|
521
|
+
*/
|
|
522
|
+
export declare function resolveFolderProjectSlotsForTest(manifest: Manifest, root: SignedEvent): string | undefined;
|
|
523
|
+
export {};
|
|
524
|
+
//# sourceMappingURL=projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"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"}
|