Highly ambitious ATProtocol AppView service and sdks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add lexicon description to network.slices.lexicon record, surface in UI and json preview, add to diff in cli when updating update

+137 -67
+4 -1
frontend/src/client.ts
··· 1 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-28 18:54:55 UTC 2 + // Generated at: 2025-09-28 21:47:20 UTC 3 3 // Lexicons: 41 4 4 5 5 /** ··· 1286 1286 export interface NetworkSlicesLexicon { 1287 1287 /** Namespaced identifier for the lexicon */ 1288 1288 nsid: string; 1289 + /** Human-readable description of the lexicon */ 1290 + description?: string; 1289 1291 /** The lexicon schema definitions as JSON */ 1290 1292 definitions: string; 1291 1293 /** When the lexicon was created */ ··· 1300 1302 1301 1303 export type NetworkSlicesLexiconSortFields = 1302 1304 | "nsid" 1305 + | "description" 1303 1306 | "definitions" 1304 1307 | "createdAt" 1305 1308 | "updatedAt"
+3 -1
frontend/src/features/slices/lexicon/handlers.tsx
··· 144 144 145 145 const sliceUri = buildSliceUri(context.currentUser.sub!, sliceId); 146 146 147 - const lexiconRecord = { 147 + const lexiconRecord: NetworkSlicesLexicon = { 148 148 nsid: lexiconData.id, 149 + description: lexiconData.description, 149 150 definitions: JSON.stringify(lexiconData.defs || lexiconData), 150 151 createdAt: new Date().toISOString(), 151 152 slice: sliceUri, ··· 251 252 sliceId: sliceParams.sliceId, 252 253 nsid: lexicon.value.nsid, 253 254 definitions: lexicon.value.definitions, 255 + description: lexicon.value.description, 254 256 uri: lexicon.uri, 255 257 createdAt: lexicon.value.createdAt, 256 258 excludedFromSync: lexicon.value.excludedFromSync === true,
+3
frontend/src/features/slices/lexicon/templates/LexiconDetailPage.tsx
··· 14 14 sliceId: string; 15 15 nsid: string; 16 16 definitions: string; 17 + description?: string; 17 18 uri: string; 18 19 createdAt: string; 19 20 excludedFromSync?: boolean; ··· 26 27 sliceId, 27 28 nsid, 28 29 definitions, 30 + description, 29 31 uri, 30 32 excludedFromSync, 31 33 currentUser, ··· 42 44 const completeLexicon = { 43 45 lexicon: 1, 44 46 id: nsid, 47 + ...(description && { description }), 45 48 defs: parsedDefinitions, 46 49 }; 47 50
+54 -40
frontend/src/features/slices/lexicon/templates/fragments/LexiconListItem.tsx
··· 9 9 nsid, 10 10 uri, 11 11 definitions, 12 + description, 12 13 excludedFromSync, 13 14 sliceId, 14 15 handle, ··· 18 19 nsid: string; 19 20 uri: string; 20 21 definitions: string; 22 + description?: string; 21 23 excludedFromSync?: boolean; 22 24 sliceId: string; 23 25 handle?: string; ··· 26 28 }) { 27 29 const rkey = getRkeyFromUri(uri); 28 30 29 - // Check the lexicon type 31 + // Check the lexicon type and extract main description as fallback 30 32 let lexiconType: string | null = null; 33 + let fallbackDescription: string | undefined = undefined; 31 34 try { 32 35 const parsedDefinitions = JSON.parse(definitions); 33 36 lexiconType = parsedDefinitions?.main?.type || null; 37 + fallbackDescription = parsedDefinitions?.main?.description; 34 38 } catch { 35 39 // If parsing fails, default to null 36 40 lexiconType = null; 37 41 } 38 42 43 + // Use top-level description or fallback to main description 44 + const displayDescription = description || fallbackDescription; 45 + 39 46 return ( 40 47 <ListItem id={`lexicon-${rkey}`}> 41 48 <div className="flex items-center w-full"> ··· 51 58 </div> 52 59 )} 53 60 <div className={`flex-1 pr-6 py-4 ${!hasSliceAccess ? "pl-6" : ""}`}> 54 - <div className="flex items-center gap-2"> 55 - <Link 56 - href={handle 57 - ? buildSliceUrl(handle, sliceId, `lexicons/${rkey}`) 58 - : `/slices/${sliceId}/lexicons/${rkey}`} 59 - variant="inherit" 60 - > 61 - <Text as="span" size="base" className="font-medium"> 62 - {nsid} 61 + <div className="space-y-1"> 62 + <div className="flex items-center gap-2"> 63 + <Link 64 + href={handle 65 + ? buildSliceUrl(handle, sliceId, `lexicons/${rkey}`) 66 + : `/slices/${sliceId}/lexicons/${rkey}`} 67 + variant="inherit" 68 + > 69 + <Text as="span" size="base" className="font-medium"> 70 + {nsid} 71 + </Text> 72 + </Link> 73 + {isPrimary !== undefined && ( 74 + <Badge variant={isPrimary ? "success" : "primary"}> 75 + {isPrimary ? "Primary" : "External"} 76 + </Badge> 77 + )} 78 + {lexiconType === "record" && ( 79 + <Badge variant="secondary"> 80 + Record 81 + </Badge> 82 + )} 83 + {lexiconType === "procedure" && ( 84 + <Badge variant="secondary"> 85 + Procedure 86 + </Badge> 87 + )} 88 + {lexiconType === "query" && ( 89 + <Badge variant="secondary"> 90 + Query 91 + </Badge> 92 + )} 93 + {(lexiconType === null || lexiconType === "object") && ( 94 + <Badge variant="secondary"> 95 + Defs 96 + </Badge> 97 + )} 98 + {excludedFromSync && ( 99 + <Badge variant="warning"> 100 + Sync disabled 101 + </Badge> 102 + )} 103 + </div> 104 + {displayDescription && ( 105 + <Text as="p" size="sm" variant="muted" className="mt-1"> 106 + {displayDescription} 63 107 </Text> 64 - </Link> 65 - {isPrimary !== undefined && ( 66 - <Badge variant={isPrimary ? "success" : "primary"}> 67 - {isPrimary ? "Primary" : "External"} 68 - </Badge> 69 - )} 70 - {lexiconType === "record" && ( 71 - <Badge variant="secondary"> 72 - Record 73 - </Badge> 74 - )} 75 - {lexiconType === "procedure" && ( 76 - <Badge variant="secondary"> 77 - Procedure 78 - </Badge> 79 - )} 80 - {lexiconType === "query" && ( 81 - <Badge variant="secondary"> 82 - Query 83 - </Badge> 84 - )} 85 - {(lexiconType === null || lexiconType === "object") && ( 86 - <Badge variant="secondary"> 87 - Defs 88 - </Badge> 89 - )} 90 - {excludedFromSync && ( 91 - <Badge variant="warning"> 92 - Sync disabled 93 - </Badge> 94 108 )} 95 109 </div> 96 110 </div>
+2
frontend/src/features/slices/lexicon/templates/fragments/LexiconsList.tsx
··· 96 96 nsid={record.value.nsid} 97 97 uri={record.uri} 98 98 definitions={record.value.definitions} 99 + description={record.value.description} 99 100 excludedFromSync={record.value.excludedFromSync} 100 101 sliceId={sliceId} 101 102 handle={handle} ··· 113 114 nsid={record.value.nsid} 114 115 uri={record.uri} 115 116 definitions={record.value.definitions} 117 + description={record.value.description} 116 118 excludedFromSync={record.value.excludedFromSync} 117 119 sliceId={sliceId} 118 120 handle={handle}
+5
lexicons/network/slices/lexicon/lexicon.json
··· 15 15 "description": "Namespaced identifier for the lexicon", 16 16 "maxLength": 256 17 17 }, 18 + "description": { 19 + "type": "string", 20 + "description": "Human-readable description of the lexicon", 21 + "maxLength": 500 22 + }, 18 23 "definitions": { 19 24 "type": "string", 20 25 "description": "The lexicon schema definitions as JSON"
+1 -1
lexicons/network/slices/waitlist/defs.json
··· 62 62 } 63 63 } 64 64 } 65 - } 65 + }
+1 -1
lexicons/network/slices/waitlist/invite.json
··· 34 34 } 35 35 } 36 36 } 37 - } 37 + }
+57 -22
packages/cli/src/commands/lexicon/push.ts
··· 1 1 import { parseArgs } from "@std/cli/parse-args"; 2 2 import { resolve } from "@std/path"; 3 - import type { AtProtoClient } from "../../generated_client.ts"; 3 + import type { 4 + AtProtoClient, 5 + NetworkSlicesLexicon, 6 + } from "../../generated_client.ts"; 4 7 import { ConfigManager } from "../../auth/config.ts"; 5 8 import { createAuthenticatedClient } from "../../utils/client.ts"; 6 9 import { logger } from "../../utils/logger.ts"; ··· 64 67 errors: [], 65 68 }; 66 69 67 - const validFiles = validationResult.files.filter(f => f.valid); 70 + const validFiles = validationResult.files.filter((f) => f.valid); 68 71 69 72 for (let i = 0; i < validFiles.length; i++) { 70 73 const file = validFiles[i]; 71 74 stats.attempted++; 72 - 73 75 74 76 try { 75 77 const lexicon = file.content as LexiconDoc & { definitions?: unknown }; ··· 81 83 try { 82 84 const response = await client.network.slices.lexicon.getRecords({ 83 85 where: { nsid: { eq: nsid } }, 84 - limit: 1 86 + limit: 1, 85 87 }); 86 - existingRecord = response.records.length > 0 ? response.records[0] : null; 88 + existingRecord = 89 + response.records.length > 0 ? response.records[0] : null; 87 90 } catch (_error) { 88 91 // Ignore error - assume lexicon doesn't exist 89 92 } ··· 92 95 // Parse existing definitions and compare with new definitions 93 96 const existingDefs = JSON.parse(existingRecord.value.definitions); 94 97 const newDefs = lexicon.defs || lexicon.definitions; 98 + const existingDescription = existingRecord.value.description; 99 + const newDescription = lexicon.description; 95 100 96 - // Deep comparison of the actual definition objects 97 - const defsEqual = JSON.stringify(existingDefs) === JSON.stringify(newDefs); 101 + // Deep comparison of definitions and description 102 + const defsEqual = 103 + JSON.stringify(existingDefs) === JSON.stringify(newDefs); 104 + const descriptionEqual = existingDescription === newDescription; 98 105 99 - if (defsEqual) { 100 - logger.info(`[DRY RUN] Would skip (unchanged): ${file.path} (${nsid})`); 106 + // Debug logging 107 + if (!defsEqual) { 108 + logger.info(`[DRY RUN] Definitions changed for ${nsid}`); 109 + } 110 + 111 + if (defsEqual && descriptionEqual) { 112 + logger.info( 113 + `[DRY RUN] Would skip (unchanged): ${file.path} (${nsid})` 114 + ); 101 115 stats.skipped++; 102 116 } else { 103 117 logger.info(`[DRY RUN] Would update: ${file.path} (${nsid})`); ··· 115 129 try { 116 130 const response = await client.network.slices.lexicon.getRecords({ 117 131 where: { nsid: { eq: nsid } }, 118 - limit: 1 132 + limit: 1, 119 133 }); 120 - existingRecord = response.records.length > 0 ? response.records[0] : null; 134 + existingRecord = 135 + response.records.length > 0 ? response.records[0] : null; 121 136 } catch (_error) { 122 137 // If getRecords fails, assume it doesn't exist and continue with create 123 138 } 124 139 125 - const lexiconRecord = { 140 + const lexiconRecord: Omit<NetworkSlicesLexicon, "createdAt" | "updatedAt"> = { 126 141 nsid: nsid, 142 + description: lexicon.description, 127 143 definitions: JSON.stringify(lexicon.defs || lexicon.definitions), 128 144 slice: sliceUri, 129 145 excludedFromSync: excludeFromSync, ··· 133 149 // Parse and compare the actual definition objects 134 150 const existingDefs = JSON.parse(existingRecord.value.definitions); 135 151 const newDefs = lexicon.defs || lexicon.definitions; 152 + const existingDescription = existingRecord.value.description; 153 + const newDescription = lexicon.description; 136 154 137 - // Deep comparison of the actual definition objects 138 - const defsEqual = JSON.stringify(existingDefs) === JSON.stringify(newDefs); 155 + // Deep comparison of definitions and description 156 + const defsEqual = 157 + JSON.stringify(existingDefs) === JSON.stringify(newDefs); 158 + const descriptionEqual = existingDescription === newDescription; 159 + 160 + // Debug logging 161 + if (!defsEqual) { 162 + logger.info(`Definitions changed for ${nsid}`); 163 + } 139 164 140 - if (defsEqual) { 165 + if (defsEqual && descriptionEqual) { 141 166 stats.skipped++; 142 167 } else { 143 168 // Update existing record ··· 148 173 }; 149 174 150 175 await client.network.slices.lexicon.updateRecord( 151 - existingRecord.uri.split('/').pop()!, // Extract record ID from URI 176 + existingRecord.uri.split("/").pop()!, // Extract record ID from URI 152 177 updateRecord 153 178 ); 154 179 ··· 178 203 return stats; 179 204 } 180 205 181 - 182 - export async function pushCommand(commandArgs: unknown[], _globalArgs: Record<string, unknown>): Promise<void> { 206 + export async function pushCommand( 207 + commandArgs: unknown[], 208 + _globalArgs: Record<string, unknown> 209 + ): Promise<void> { 183 210 const args = parseArgs(commandArgs as string[], { 184 211 boolean: ["help", "validate-only", "dry-run", "exclude-from-sync"], 185 212 string: ["path", "slice", "api-url"], ··· 202 229 if (!args["validate-only"] && !mergedConfig.slice) { 203 230 logger.error("--slice is required unless using --validate-only"); 204 231 if (!slicesConfig.slice) { 205 - logger.info("Tip: Create a slices.json file with your slice URI to avoid passing --slice every time"); 232 + logger.info( 233 + "Tip: Create a slices.json file with your slice URI to avoid passing --slice every time" 234 + ); 206 235 } 207 236 console.log("\nRun 'slices lexicon push --help' for usage information."); 208 237 Deno.exit(1); ··· 268 297 } 269 298 270 299 if (dryRun) { 271 - logger.success(`DRY RUN complete - ${importStats.created + importStats.updated} files would be processed`); 300 + logger.success( 301 + `DRY RUN complete - ${ 302 + importStats.created + importStats.updated 303 + } files would be processed` 304 + ); 272 305 } else { 273 306 const total = importStats.created + importStats.updated; 274 307 if (total > 0) { 275 - logger.success(`Pushed ${total} lexicons (${importStats.created} created, ${importStats.updated} updated)`); 308 + logger.success( 309 + `Pushed ${total} lexicons (${importStats.created} created, ${importStats.updated} updated)` 310 + ); 276 311 } else { 277 312 logger.success("All lexicons up to date"); 278 313 } 279 314 } 280 - } 315 + }
+4 -1
packages/cli/src/generated_client.ts
··· 1 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-28 18:56:39 UTC 2 + // Generated at: 2025-09-28 21:51:06 UTC 3 3 // Lexicons: 41 4 4 5 5 /** ··· 1286 1286 export interface NetworkSlicesLexicon { 1287 1287 /** Namespaced identifier for the lexicon */ 1288 1288 nsid: string; 1289 + /** Human-readable description of the lexicon */ 1290 + description?: string; 1289 1291 /** The lexicon schema definitions as JSON */ 1290 1292 definitions: string; 1291 1293 /** When the lexicon was created */ ··· 1300 1302 1301 1303 export type NetworkSlicesLexiconSortFields = 1302 1304 | "nsid" 1305 + | "description" 1303 1306 | "definitions" 1304 1307 | "createdAt" 1305 1308 | "updatedAt"
+3
packages/lexicon/lexicon.ts
··· 61 61 /** Unique namespace identifier (NSID) for this lexicon */ 62 62 id: string; 63 63 64 + /** Human-readable description of the lexicon */ 65 + description?: string; 66 + 64 67 /** Schema definitions mapping definition names to their schemas */ 65 68 defs: Record<string, LexiconDefinition>; 66 69