@recursiv/sdk 0.7.74 → 0.7.77
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 +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/resources/discovery.d.ts +4 -4
- package/dist/resources/posts.d.ts +25 -0
- package/dist/resources/posts.js +39 -0
- package/dist/resources/protocols.d.ts +9 -0
- package/llm.md +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
93
93
|
### Social
|
|
94
94
|
| Resource | Key methods |
|
|
95
95
|
|----------|------------|
|
|
96
|
-
| `r.posts` | `create`, `update`, `list`, `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
|
|
96
|
+
| `r.posts` | `create`, `update`, `list`, `get`, `getMany(ids)` (up to 25 posts by id per request, no thread; a `Map` of each id's post or `NotFoundError`; 0.7.77), `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
|
|
97
97
|
| `r.bookmarks` | `save`, `unsave`, `posts`, `list`, `keys`, `merge` — the member's saved posts, open-network posts and articles, synced across devices; `collections`: `list`, `create`, `update`, `remove`, `add`, `removeItem`, `items`, `posts` — private named collections of saves (0.7.68, where `network.getConfig()` advertises `capabilities.bookmark_collections`) |
|
|
98
98
|
| `r.feeds` | `top` (what the member is most likely to engage with, rotated by what they saw, with open-network lanes and the lead story), `stories` (the story tail), `publicTop` (a visitor's top, keyless and edge-cacheable) |
|
|
99
99
|
| `r.communities` | `create`, `join`, `search`, `members` |
|
|
@@ -120,7 +120,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
120
120
|
| `r.moderation` | Moderation actions: `publicLog`, `mine`, `appeal`; staff `apply`, `resolveAppeal`, `list({ target_type?, target_id? })` (one target's recorded actions, newest first, at most 100; none named = the network's latest; 0.7.74) and `reverse(actionId)` (sent once: a repeat answers 404 `action_not_found`; 0.7.74); agent grants `listAgentGrants`, `grantAgent`, `revokeAgent` |
|
|
121
121
|
| `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
|
|
122
122
|
| `r.goals` | Org goals with task rollups (group dispatcher tasks) |
|
|
123
|
-
| `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that left the radar within its minimum lifetime, or within the last half hour, still answers live with `liveUntil` and the story that continues it; one that has left for longer comes with its archived copy and the live story it continues as; the id may be a World Today edition story's, and the answer then names it in `editionStory` under the story's radar id), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })` (keyless reads carry images and videos only from outlets and members marked `visitorMedia`; signed-in reads keep all media, so a share card uses only those); the Viral read (fastest-moving posts across Bluesky, the Fediverse, Nostr and Minds, each against its own network's pace; 0.7.59): `getPublicViral({ network?, category?, media?: 'video', limit?: 5 | 10 | 20 | 40, cursor?, compass? })` under the member's key and keyless `getSharedViral(projectId, params)` (visitors: `compass` `all` or `hide` only; a visitor's `media: 'video'` read can carry items whose video the server withheld, so filter with the exported `viralItemHasVideo` before a video player; `item.post` narrows on `item.protocol`). `status: 'off'` resolves with no items until the app enables the read, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401 and 403, so a session failure is never read as off; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
|
|
123
|
+
| `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that left the radar within its minimum lifetime, or within the last half hour, still answers live with `liveUntil` and the story that continues it; one that has left for longer comes with its archived copy and the live story it continues as; the id may be a World Today edition story's, and the answer then names it in `editionStory` under the story's radar id; a story a World Today story seeded carries `volume`, the first page of the posts that story counts across Minds and the open networks, whose `cursor` goes on with `curator.storyPosts` or `curator.memberStoryPosts`), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })` (keyless reads carry images and videos only from outlets and members marked `visitorMedia`; signed-in reads keep all media, so a share card uses only those); the Viral read (fastest-moving posts across Bluesky, the Fediverse, Nostr and Minds, each against its own network's pace; 0.7.59): `getPublicViral({ network?, category?, media?: 'video', limit?: 5 | 10 | 20 | 40, cursor?, compass? })` under the member's key and keyless `getSharedViral(projectId, params)` (visitors: `compass` `all` or `hide` only; a visitor's `media: 'video'` read can carry items whose video the server withheld, so filter with the exported `viralItemHasVideo` before a video player; `item.post` narrows on `item.protocol`). `status: 'off'` resolves with no items until the app enables the read, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401 and 403, so a session failure is never read as off; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
|
|
124
124
|
|
|
125
125
|
Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
|
|
126
126
|
|
package/dist/index.d.ts
CHANGED
|
@@ -158,8 +158,8 @@ export { RealtimeClient } from './realtime.js';
|
|
|
158
158
|
export type { RealtimeConnectOptions } from './realtime.js';
|
|
159
159
|
export { AuthResource } from './resources/auth.js';
|
|
160
160
|
export type { AuthSession, CurrentSession, SignUpInput, SignInInput, UsernameSignInInput, AppContext, ResetPasswordInput, TwoFactorConfirmation, TwoFactorEnrollment, TwoFactorChallenge, CompleteTwoFactorSignInInput, OAuthClientInfo, SendOtpInput, VerifyOtpInput, CreateApiKeyInput, ApiKeyResult, LegacyHandoffOptions, LegacyHandoffSession, } from './resources/auth.js';
|
|
161
|
-
export { PostsResource } from './resources/posts.js';
|
|
162
|
-
export type { CategoryOrder, CategoryOrderMeta, CategoryOrderReason } from './resources/posts.js';
|
|
161
|
+
export { POSTS_BATCH_MAX, PostsResource } from './resources/posts.js';
|
|
162
|
+
export type { CategoryOrder, CategoryOrderMeta, CategoryOrderReason, PostBatch, PostBatchItem } from './resources/posts.js';
|
|
163
163
|
export { UsersResource } from './resources/users.js';
|
|
164
164
|
export { CommunitiesResource } from './resources/communities.js';
|
|
165
165
|
export { ChatResource } from './resources/chat.js';
|
package/dist/index.js
CHANGED
|
@@ -229,7 +229,7 @@ export class Recursiv {
|
|
|
229
229
|
export { HttpClient } from './client.js';
|
|
230
230
|
export { RealtimeClient } from './realtime.js';
|
|
231
231
|
export { AuthResource } from './resources/auth.js';
|
|
232
|
-
export { PostsResource } from './resources/posts.js';
|
|
232
|
+
export { POSTS_BATCH_MAX, PostsResource } from './resources/posts.js';
|
|
233
233
|
export { UsersResource } from './resources/users.js';
|
|
234
234
|
export { CommunitiesResource } from './resources/communities.js';
|
|
235
235
|
export { ChatResource } from './resources/chat.js';
|
|
@@ -23,10 +23,10 @@ export interface WatchVideosParams {
|
|
|
23
23
|
/** A position in the list to start a first read at (0 to 400); a later page sends `cursor`. */
|
|
24
24
|
offset?: number;
|
|
25
25
|
/**
|
|
26
|
-
* A Politics choice, spelled as `posts.list` takes it. Only Hide politics (`hide`) is applied here: the
|
|
27
|
-
* browse category is politics
|
|
28
|
-
* fill it, with or without compass judgments on the server. A
|
|
29
|
-
* `unavailable`. The page says what it did in `meta.compass_filter`.
|
|
26
|
+
* A Politics choice, spelled as `posts.list` takes it. Only Hide politics (`hide`) is applied here, strictly: the
|
|
27
|
+
* videos whose browse category is politics, whose compass judgment calls them political or maybe, or whose title or
|
|
28
|
+
* words name politics leave the page and the next ones fill it, with or without compass judgments on the server. A
|
|
29
|
+
* side or Mix is answered unfiltered, marked `unavailable`. The page says what it did in `meta.compass_filter`.
|
|
30
30
|
*/
|
|
31
31
|
compass?: string;
|
|
32
32
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
+
import { RecursivError } from '../errors.js';
|
|
2
3
|
import type { PaginatedResponse, SingleResponse, DeleteResponse, Post, PostDetail, CreatePostInput, CreatedPost, UpdatePostInput, ListPostsParams, ListPostRepliesParams, PostRepliesPage, SearchPostsParams, SearchPostsResponse, ReactionType } from '../types.js';
|
|
3
4
|
import type { BookmarkedPost, BookmarksPage, SavedBookmark, UnsavedBookmark } from './bookmarks.js';
|
|
4
5
|
/** How GET /posts/categories ordered its answer: the member's own order, the app's popularity order, or the fixed order. */
|
|
@@ -14,6 +15,21 @@ export interface CategoryOrderMeta {
|
|
|
14
15
|
/** Whether For You can personalize for this member yet; absent when the member's order was not read. */
|
|
15
16
|
personalized?: boolean;
|
|
16
17
|
}
|
|
18
|
+
/** The most ids one GET /posts/batch request carries: `posts.getMany` sends one request per this many (0.7.77). */
|
|
19
|
+
export declare const POSTS_BATCH_MAX = 25;
|
|
20
|
+
/** A post `posts.getMany` read: the post as `posts.get` serves it to the caller, without its thread (no `replies`). */
|
|
21
|
+
export type PostBatchItem = Omit<PostDetail, 'replies' | 'replies_next_cursor'>;
|
|
22
|
+
/**
|
|
23
|
+
* What `posts.getMany` found for each id it read: the post, or the error that id's read answered. A `NotFoundError`
|
|
24
|
+
* (status 404) is a post that is gone, was never there, or is not for this caller, as `posts.get` answers it.
|
|
25
|
+
*/
|
|
26
|
+
export type PostBatch = Map<string, {
|
|
27
|
+
data: PostBatchItem;
|
|
28
|
+
error?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
data?: undefined;
|
|
31
|
+
error: RecursivError;
|
|
32
|
+
}>;
|
|
17
33
|
export declare class PostsResource {
|
|
18
34
|
private client;
|
|
19
35
|
constructor(client: HttpClient);
|
|
@@ -54,6 +70,15 @@ export declare class PostsResource {
|
|
|
54
70
|
}): Promise<PaginatedResponse<Post>>;
|
|
55
71
|
/** Get a single post with its newest replies; `replies_next_cursor` is set when the thread continues. */
|
|
56
72
|
get(id: string): Promise<SingleResponse<PostDetail>>;
|
|
73
|
+
/**
|
|
74
|
+
* Several posts by id, one request per POSTS_BATCH_MAX ids (GET /posts/batch; 0.7.77), each id answered on its own:
|
|
75
|
+
* the post as `get` serves it to the caller, `viewer` and the caller's own vote and repost included, without its
|
|
76
|
+
* thread (the server reads no replies and no chain for it, so a batch of six answers about as fast as one post), or
|
|
77
|
+
* the error that read answered (`NotFoundError` for one that is gone or not for this caller). Each id is asked once
|
|
78
|
+
* (blank ones are dropped) and the Map is keyed by the ids as given. A request that fails as a whole throws, as an
|
|
79
|
+
* API without this route does (404): read those ids one by one with `get`.
|
|
80
|
+
*/
|
|
81
|
+
getMany(ids: readonly string[]): Promise<PostBatch>;
|
|
57
82
|
/** One page of a post's replies, newest first. Start from `replies_next_cursor` on `get`, then follow `meta.next_cursor`. */
|
|
58
83
|
replies(id: string, params?: ListPostRepliesParams): Promise<PostRepliesPage>;
|
|
59
84
|
/** Create a new post */
|
package/dist/resources/posts.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { errorBodyFromResponse, NotFoundError, RecursivError } from '../errors.js';
|
|
2
|
+
/** The most ids one GET /posts/batch request carries: `posts.getMany` sends one request per this many (0.7.77). */
|
|
3
|
+
export const POSTS_BATCH_MAX = 25;
|
|
4
|
+
/** One entry's error as the error `posts.get` throws for it. */
|
|
5
|
+
function postBatchError(error) {
|
|
6
|
+
const status = error && typeof error === 'object' && typeof error.status === 'number'
|
|
7
|
+
? error.status : 404;
|
|
8
|
+
const body = errorBodyFromResponse(status, undefined, { error });
|
|
9
|
+
return status === 404 ? new NotFoundError(body) : new RecursivError(status, body);
|
|
10
|
+
}
|
|
1
11
|
export class PostsResource {
|
|
2
12
|
client;
|
|
3
13
|
constructor(client) {
|
|
@@ -48,6 +58,35 @@ export class PostsResource {
|
|
|
48
58
|
get(id) {
|
|
49
59
|
return this.client.get(`/posts/${id}`);
|
|
50
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Several posts by id, one request per POSTS_BATCH_MAX ids (GET /posts/batch; 0.7.77), each id answered on its own:
|
|
63
|
+
* the post as `get` serves it to the caller, `viewer` and the caller's own vote and repost included, without its
|
|
64
|
+
* thread (the server reads no replies and no chain for it, so a batch of six answers about as fast as one post), or
|
|
65
|
+
* the error that read answered (`NotFoundError` for one that is gone or not for this caller). Each id is asked once
|
|
66
|
+
* (blank ones are dropped) and the Map is keyed by the ids as given. A request that fails as a whole throws, as an
|
|
67
|
+
* API without this route does (404): read those ids one by one with `get`.
|
|
68
|
+
*/
|
|
69
|
+
async getMany(ids) {
|
|
70
|
+
const unique = [...new Set(ids.filter((id) => typeof id === 'string' && id.trim()))];
|
|
71
|
+
const found = new Map();
|
|
72
|
+
for (let start = 0; start < unique.length; start += POSTS_BATCH_MAX) {
|
|
73
|
+
const chunk = unique.slice(start, start + POSTS_BATCH_MAX);
|
|
74
|
+
const answer = await this.client.get('/posts/batch', { ids: chunk.join(',') });
|
|
75
|
+
// A uuid's case is not part of it: the server answers each id once, by its first spelling.
|
|
76
|
+
const entries = new Map((Array.isArray(answer?.data) ? answer.data : [])
|
|
77
|
+
.filter((entry) => entry && typeof entry.id === 'string')
|
|
78
|
+
.map((entry) => [entry.id.trim().toLowerCase(), entry]));
|
|
79
|
+
for (const id of chunk) {
|
|
80
|
+
const entry = entries.get(id.trim().toLowerCase());
|
|
81
|
+
if (!entry) {
|
|
82
|
+
throw new RecursivError(502, { type: 'api_error', code: 'invalid_response',
|
|
83
|
+
message: 'GET /posts/batch did not answer every id it was asked; read them one by one with posts.get' });
|
|
84
|
+
}
|
|
85
|
+
found.set(id, entry.data && !entry.error ? { data: entry.data } : { error: postBatchError(entry.error) });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
51
90
|
/** One page of a post's replies, newest first. Start from `replies_next_cursor` on `get`, then follow `meta.next_cursor`. */
|
|
52
91
|
replies(id, params) {
|
|
53
92
|
return this.client.get(`/posts/${encodeURIComponent(id)}/replies`, params ? { limit: params.limit, cursor: params.cursor } : undefined);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import type { CompassFilterMeta, DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse, StoryOutlets } from '../types.js';
|
|
3
|
+
import type { CuratorStoryPostsPage } from './curator.js';
|
|
3
4
|
/** Canonical protocol operation names returned by the adapter registry. */
|
|
4
5
|
export declare const PROTOCOL_CAPABILITY_OPERATIONS: readonly ["discover", "stream", "backfill", "search", "resolveIdentity", "fetchProfile", "fetchThread", "publish", "reply", "react", "repost", "follow", "delete", "health", "checkpoint"];
|
|
5
6
|
export declare const PROTOCOL_CAPABILITY_STATUSES: readonly ["supported", "planned", "unsupported"];
|
|
@@ -1509,6 +1510,14 @@ export interface SocialRadarStory {
|
|
|
1509
1510
|
* seeded, and `id` is its radar id, the address to link to. A server that cannot resolve edition story ids answers 400.
|
|
1510
1511
|
*/
|
|
1511
1512
|
editionStory?: string;
|
|
1513
|
+
/**
|
|
1514
|
+
* The posts the World Today story that seeded this story counts across Minds, Bluesky, the Fediverse and Nostr: the
|
|
1515
|
+
* first page of the reader's pool, exactly as `curator.storyPosts` (signed out) or `curator.memberStoryPosts` (signed
|
|
1516
|
+
* in) serves it for `volume.story_key`, whose `cursor` continues there (or walk it with `curator.storyPostPages`).
|
|
1517
|
+
* `volume.discussion` is the count the pages list toward. Absent while the server does not serve World Story Volume
|
|
1518
|
+
* for the app, and for a story no World Today story seeded or whose pool holds no post for this reader.
|
|
1519
|
+
*/
|
|
1520
|
+
volume?: CuratorStoryPostsPage;
|
|
1512
1521
|
}
|
|
1513
1522
|
/**
|
|
1514
1523
|
* The Viral read (`getPublicViral`, `getSharedViral`): what is moving fastest on Bluesky, the Fediverse, Nostr and
|
package/llm.md
CHANGED
|
@@ -630,6 +630,8 @@ const { data: results } = await r.posts.search({ query: 'shipped', community_id:
|
|
|
630
630
|
|
|
631
631
|
Methods: `list`, `get`, `create`, `update`, `delete`, `search`, `replies(id, { limit, cursor })` (after `replies_next_cursor` on `get`), `react`, `unreact`, `liked`, `bookmarked`, `bookmark`, `unbookmark`
|
|
632
632
|
|
|
633
|
+
Several posts by id (0.7.77): `const found = await r.posts.getMany(ids)` reads up to `POSTS_BATCH_MAX` (25) per request (`GET /posts/batch`) and returns a `Map` keyed by id: `found.get(id)?.data` is the post as `get` serves it to the member (`viewer`, `user_reaction`, `reposted_by_viewer_id`), without its thread (no `replies`; no thread is read, so a story page's six posts come back in about the time of one), and `found.get(id)?.error` is a `NotFoundError` for one that is gone or not for this member. Each id is asked once. The call throws when the request fails as a whole, as on an API without the route (404): read those ids one by one with `get`.
|
|
634
|
+
|
|
633
635
|
Feed definitions (types only; no route serves one yet): `FeedDefinition` is `{ v: 1, sources, ranking: { mode, range? }, filters?, exclude? }`, with 1-`FEED_SOURCES_MAX` (50) sources read as one union (`FEED_SOURCE_KINDS`; `FEED_SOURCE_KEY[kind]` names each kind's one field, e.g. `{ kind: 'tag', slug: 'rust' }`, `{ kind: 'account', ref: 'did:plc:…' }`). `filters.politics` stores only `'hide'`; viewer settings, blocks, mutes and compass Mix/Sides never go in a definition. Built-in feeds: `builtinCategoryFeedRef('science')` → `'builtin:category:science'`, plus `'builtin:all'` and `'builtin:for_you'`.
|
|
634
636
|
|
|
635
637
|
`search` ranks relevance x recency x engagement (recency falls as a power of age: this month outranks years ago) through the promotion safety floor; `sort: 'new'` lists newest first; `media: 'video'` (0.7.69) keeps only posts with a video that plays (relevance only), in the page shape Watch reads, so a player can play a query. Page with `meta.next_cursor` as `cursor`: later relevance pages are slices of the ranking the first page kept for ten minutes (nothing skipped or repeated while it lives; after that, the page continues past the last post's score boundary), newest-first pages continue by (created_at, id). A cursor is bound to its search and sort: another search's, or the other sort's, is 400 `invalid_cursor`. The first relevance page carries `latest` (up to three of the newest matches not on the page, one per author, for a Latest strip); the last page carries `fall_over.lanes` (`new`, `open_network`, `tag`): draw a way on, not a wall.
|