From 74817bfcc67f487aafd51fb2c3df38e51e81b0ec Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Wed, 1 Apr 2026 12:41:04 -0700 Subject: [PATCH] update parts.page lexicons --- .../[command]/get_user_mention_services.ts | 1 + app/api/rpc/[command]/proxy_mention_search.ts | 5 +- lexicons/api/lexicons.ts | 52 +++++++++++++++++++ .../api/types/parts/page/mention/search.ts | 40 ++++++++++++++ .../api/types/parts/page/mention/service.ts | 1 + lexicons/parts/page/mention/search.json | 47 +++++++++++++++++ lexicons/parts/page/mention/service.json | 3 ++ lexicons/src/mentionService.ts | 45 ++++++++++++++++ 8 files changed, 193 insertions(+), 1 deletion(-) diff --git a/app/api/rpc/[command]/get_user_mention_services.ts b/app/api/rpc/[command]/get_user_mention_services.ts index 8a3012f6..3e28996a 100644 --- a/app/api/rpc/[command]/get_user_mention_services.ts +++ b/app/api/rpc/[command]/get_user_mention_services.ts @@ -42,6 +42,7 @@ export const get_user_mention_services = makeRoute({ name: record.name, description: record.description, did: record.did, + canBeScopedToDid: record.canBeScopedToDid ?? false, }; }), }, diff --git a/app/api/rpc/[command]/proxy_mention_search.ts b/app/api/rpc/[command]/proxy_mention_search.ts index 679f90f1..cef4260e 100644 --- a/app/api/rpc/[command]/proxy_mention_search.ts +++ b/app/api/rpc/[command]/proxy_mention_search.ts @@ -16,9 +16,10 @@ export const proxy_mention_search = makeRoute({ input: z.object({ service_uri: z.string(), search: z.string(), + scope: z.string().optional(), }), handler: async ( - { service_uri, search }, + { service_uri, search, scope }, { supabase }: Pick, ) => { try { @@ -47,6 +48,7 @@ export const proxy_mention_search = makeRoute({ const response = await agent.call("parts.page.mention.search", { service: service_uri, search, + ...(scope ? { scope } : {}), }); const data = response.data as SearchService.OutputSchema | undefined; @@ -62,6 +64,7 @@ export const proxy_mention_search = makeRoute({ href: r.href, icon: r.icon, embed: r.embed, + subscope: r.subscope, })), }, }; diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts index 9b4772f3..cb926a10 100644 --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -1064,6 +1064,11 @@ export const schemaDict = { type: 'string', description: 'Search query string', }, + scope: { + type: 'string', + description: + "Optional scope identifier to narrow results within a service, as returned by a previous result's subscope.scope field", + }, limit: { type: 'integer', minimum: 1, @@ -1103,6 +1108,19 @@ export const schemaDict = { type: 'string', description: 'Display name for the mentioned entity', }, + description: { + type: 'string', + description: 'A description for the mentioned entity', + }, + labels: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:parts.page.mention.search#mentionLabel', + }, + description: + 'A set of labels to be rendered with the mentionedEntity', + }, href: { type: 'string', format: 'uri', @@ -1120,6 +1138,20 @@ export const schemaDict = { description: 'Optional embed info for creating an embed block instead of an inline mention', }, + subscope: { + type: 'ref', + ref: 'lex:parts.page.mention.search#subscopeInfo', + description: + 'Optional subscope info indicating this result can be scoped into for further searching', + }, + }, + }, + mentionLabel: { + type: 'object', + properties: { + text: { + type: 'string', + }, }, }, embedInfo: { @@ -1145,6 +1177,23 @@ export const schemaDict = { }, }, }, + subscopeInfo: { + type: 'object', + required: ['scope', 'label'], + properties: { + scope: { + type: 'string', + description: + 'Scope identifier passed back to the service in subsequent search queries', + }, + label: { + type: 'string', + maxLength: 100, + description: + "Display label for the scope-down button (e.g. 'Posts', 'Tracks')", + }, + }, + }, }, }, PartsPageMentionService: { @@ -1168,6 +1217,9 @@ export const schemaDict = { type: 'string', maxLength: 2000, }, + canBeScopedToDid: { + type: 'boolean', + }, did: { type: 'string', format: 'did', diff --git a/lexicons/api/types/parts/page/mention/search.ts b/lexicons/api/types/parts/page/mention/search.ts index d5c3ac22..53f69fe6 100644 --- a/lexicons/api/types/parts/page/mention/search.ts +++ b/lexicons/api/types/parts/page/mention/search.ts @@ -20,6 +20,8 @@ export type QueryParams = { service: string /** Search query string */ search: string + /** Optional scope identifier to narrow results within a service, as returned by a previous result's subscope.scope field */ + scope?: string /** Maximum number of results to return */ limit?: number } @@ -50,11 +52,16 @@ export interface Result { uri: string /** Display name for the mentioned entity */ name: string + /** A description for the mentioned entity */ + description?: string + /** A set of labels to be rendered with the mentionedEntity */ + labels?: MentionLabel[] /** Optional web URL for the mentioned entity */ href?: string /** Optional icon URL for the mentioned entity, displayed next to the mention */ icon?: string embed?: EmbedInfo + subscope?: SubscopeInfo } const hashResult = 'result' @@ -67,6 +74,21 @@ export function validateResult(v: V) { return validate(v, id, hashResult) } +export interface MentionLabel { + $type?: 'parts.page.mention.search#mentionLabel' + text?: string +} + +const hashMentionLabel = 'mentionLabel' + +export function isMentionLabel(v: V) { + return is$typed(v, id, hashMentionLabel) +} + +export function validateMentionLabel(v: V) { + return validate(v, id, hashMentionLabel) +} + export interface EmbedInfo { $type?: 'parts.page.mention.search#embedInfo' /** Source URL for the iframe embed */ @@ -86,3 +108,21 @@ export function isEmbedInfo(v: V) { export function validateEmbedInfo(v: V) { return validate(v, id, hashEmbedInfo) } + +export interface SubscopeInfo { + $type?: 'parts.page.mention.search#subscopeInfo' + /** Scope identifier passed back to the service in subsequent search queries */ + scope: string + /** Display label for the scope-down button (e.g. 'Posts', 'Tracks') */ + label: string +} + +const hashSubscopeInfo = 'subscopeInfo' + +export function isSubscopeInfo(v: V) { + return is$typed(v, id, hashSubscopeInfo) +} + +export function validateSubscopeInfo(v: V) { + return validate(v, id, hashSubscopeInfo) +} diff --git a/lexicons/api/types/parts/page/mention/service.ts b/lexicons/api/types/parts/page/mention/service.ts index 8ae5dea8..d16a08a4 100644 --- a/lexicons/api/types/parts/page/mention/service.ts +++ b/lexicons/api/types/parts/page/mention/service.ts @@ -18,6 +18,7 @@ export interface Record { $type: 'parts.page.mention.service' name: string description?: string + canBeScopedToDid?: boolean /** DID of the service that implements parts.page.mention.search */ did: string [k: string]: unknown diff --git a/lexicons/parts/page/mention/search.json b/lexicons/parts/page/mention/search.json index 16a1aada..fbefd9a2 100644 --- a/lexicons/parts/page/mention/search.json +++ b/lexicons/parts/page/mention/search.json @@ -21,6 +21,10 @@ "type": "string", "description": "Search query string" }, + "scope": { + "type": "string", + "description": "Optional scope identifier to narrow results within a service, as returned by a previous result's subscope.scope field" + }, "limit": { "type": "integer", "minimum": 1, @@ -65,6 +69,18 @@ "type": "string", "description": "Display name for the mentioned entity" }, + "description": { + "type": "string", + "description": "A description for the mentioned entity" + }, + "labels": { + "type": "array", + "items": { + "type": "ref", + "ref": "#mentionLabel" + }, + "description": "A set of labels to be rendered with the mentionedEntity" + }, "href": { "type": "string", "format": "uri", @@ -79,6 +95,19 @@ "type": "ref", "ref": "#embedInfo", "description": "Optional embed info for creating an embed block instead of an inline mention" + }, + "subscope": { + "type": "ref", + "ref": "#subscopeInfo", + "description": "Optional subscope info indicating this result can be scoped into for further searching" + } + } + }, + "mentionLabel": { + "type": "object", + "properties": { + "text": { + "type": "string" } } }, @@ -106,6 +135,24 @@ "description": "Default height of the embed in pixels" } } + }, + "subscopeInfo": { + "type": "object", + "required": [ + "scope", + "label" + ], + "properties": { + "scope": { + "type": "string", + "description": "Scope identifier passed back to the service in subsequent search queries" + }, + "label": { + "type": "string", + "maxLength": 100, + "description": "Display label for the scope-down button (e.g. 'Posts', 'Tracks')" + } + } } } } \ No newline at end of file diff --git a/lexicons/parts/page/mention/service.json b/lexicons/parts/page/mention/service.json index 627a399f..0679fc5b 100644 --- a/lexicons/parts/page/mention/service.json +++ b/lexicons/parts/page/mention/service.json @@ -21,6 +21,9 @@ "type": "string", "maxLength": 2000 }, + "canBeScopedToDid": { + "type": "boolean" + }, "did": { "type": "string", "format": "did", diff --git a/lexicons/src/mentionService.ts b/lexicons/src/mentionService.ts index 7dab2c15..5923dc75 100644 --- a/lexicons/src/mentionService.ts +++ b/lexicons/src/mentionService.ts @@ -15,6 +15,7 @@ export const PagePartsMentionService: LexiconDoc = { properties: { name: { type: "string", maxLength: 200 }, description: { type: "string", maxLength: 2000 }, + canBeScopedToDid: { type: "boolean" }, did: { type: "string", format: "did", @@ -49,6 +50,11 @@ export const PagePartsMentionSearchService: LexiconDoc = { type: "string", description: "Search query string", }, + scope: { + type: "string", + description: + "Optional scope identifier to narrow results within a service, as returned by a previous result's subscope.scope field", + }, limit: { type: "integer", minimum: 1, @@ -85,6 +91,16 @@ export const PagePartsMentionSearchService: LexiconDoc = { type: "string", description: "Display name for the mentioned entity", }, + description: { + type: "string", + description: "A description for the mentioned entity", + }, + labels: { + type: "array", + items: { type: "ref", ref: "#mentionLabel" }, + description: + "A set of labels to be rendered with the mentionedEntity", + }, href: { type: "string", format: "uri", @@ -102,6 +118,18 @@ export const PagePartsMentionSearchService: LexiconDoc = { description: "Optional embed info for creating an embed block instead of an inline mention", }, + subscope: { + type: "ref", + ref: "#subscopeInfo", + description: + "Optional subscope info indicating this result can be scoped into for further searching", + }, + }, + }, + mentionLabel: { + type: "object", + properties: { + text: { type: "string" }, }, }, embedInfo: { @@ -127,6 +155,23 @@ export const PagePartsMentionSearchService: LexiconDoc = { }, }, }, + subscopeInfo: { + type: "object", + required: ["scope", "label"], + properties: { + scope: { + type: "string", + description: + "Scope identifier passed back to the service in subsequent search queries", + }, + label: { + type: "string", + maxLength: 100, + description: + "Display label for the scope-down button (e.g. 'Posts', 'Tracks')", + }, + }, + }, }, }; -- 2.51.2