👁️
5
fork

Configure Feed

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

more lexicon work

+293 -1
+212
.claude/TYPELEX.md
··· 1 + # Typelex Reference 2 + 3 + Typelex is a TypeSpec-based syntax for authoring AT Protocol Lexicon schemas. It compiles `.tsp` files to Lexicon JSON. 4 + 5 + ## Basic Structure 6 + 7 + ```typescript 8 + import "@typelex/emitter"; 9 + import "./externals.tsp"; 10 + 11 + namespace com.example.schema { 12 + @rec("tid") 13 + model Main { 14 + @required 15 + text: string; 16 + } 17 + } 18 + ``` 19 + 20 + ## Record Types 21 + 22 + Use `@rec()` decorator to define record types: 23 + 24 + - `@rec("tid")` - Timestamp-based IDs for collections 25 + - `@rec("literal:self")` - Single record per repo (profiles) 26 + - `@rec("any")` - Arbitrary keys 27 + 28 + ## Property Decorators 29 + 30 + ### Constraints 31 + - `@required` - Mandatory field 32 + - `@maxGraphemes(n)` - Visual character limit (user-facing) 33 + - `@maxLength(n)` - Byte length limit (typically 10x graphemes for UTF-8) 34 + - `@minValue(n)` / `@maxValue(n)` - Integer bounds 35 + - `@minItems(n)` / `@maxItems(n)` - Array size limits 36 + - `@minLength(n)` - Minimum string/array length 37 + 38 + ### Special Decorators 39 + - `@inline` - Expand model inline without separate definition 40 + - `@token` - Create empty token models (for marker types) 41 + - `@external` - Declare external namespace stub 42 + 43 + ## Type System 44 + 45 + ### Primitives 46 + - `string`, `integer`, `boolean`, `bytes` 47 + - `datetime` - AT Protocol datetime format 48 + - `did`, `handle`, `atUri` - AT Protocol identifiers 49 + - `cid` - Content identifier 50 + - `uri` - Standard URI 51 + 52 + ### Collections 53 + - Arrays: `string[]`, `Model[]` 54 + - Optional: `field?: type` 55 + 56 + ### Unions 57 + 58 + **Open unions (recommended):** 59 + ```typescript 60 + // String with known values 61 + section: "mainboard" | "sideboard" | "maybeboard" | string; 62 + 63 + // Model union with extensibility 64 + features: (Mention | Link | Tag | unknown)[]; 65 + ``` 66 + 67 + Compiles to `knownValues` in JSON for string unions. 68 + 69 + **Closed enums (discouraged):** 70 + Avoid unless absolutely necessary. Use open unions instead. 71 + 72 + ## External References 73 + 74 + To reference external AT Protocol lexicons (like `com.atproto.repo.strongRef`): 75 + 76 + 1. **Download the external lexicon JSON** to `lexicons/` folder: 77 + ```bash 78 + mkdir -p lexicons/com/atproto/repo 79 + curl -s https://raw.githubusercontent.com/bluesky-social/atproto/main/lexicons/com/atproto/repo/strongRef.json \ 80 + > lexicons/com/atproto/repo/strongRef.json 81 + ``` 82 + 83 + 2. **Run typelex** - it auto-generates `typelex/externals.tsp` with `@external` stubs: 84 + ```bash 85 + npm run build:typelex 86 + ``` 87 + 88 + 3. **Reference the external type** using the full namespace + `.Main`: 89 + ```typescript 90 + namespace com.deckbelcher.social.like { 91 + model Main { 92 + @required 93 + subject: com.atproto.repo.strongRef.Main; 94 + } 95 + } 96 + ``` 97 + 98 + **Important:** Typelex uses the full lexicon ID as the namespace (e.g., `com.atproto.repo.strongRef`), so you reference it as `com.atproto.repo.strongRef.Main`, not `com.atproto.repo.strongRef`. 99 + 100 + ### How externals.tsp Works 101 + 102 + Starting with typelex v0.3.0+: 103 + - Automatically generates `typelex/externals.tsp` based on JSON files in `lexicons/` 104 + - Only includes external lexicons (not your app's namespace) 105 + - Uses `@external` decorator to skip JSON output for those namespaces 106 + - Must be imported in `typelex/main.tsp` entry point 107 + 108 + Generated stub example: 109 + ```typescript 110 + @external 111 + namespace com.atproto.repo.strongRef { 112 + model Main { } 113 + } 114 + ``` 115 + 116 + ## File Organization 117 + 118 + - Source files: `typelex/*.tsp` 119 + - Output: `lexicons/` directory 120 + - Import pattern: `import "./other-file.tsp"` 121 + - Namespaces determine output structure, not file organization 122 + 123 + ## Compilation 124 + 125 + ```bash 126 + npm run build:typelex 127 + # runs: typelex compile com.deckbelcher.* 128 + ``` 129 + 130 + Output is deterministic JSON in `lexicons/` matching namespace structure. 131 + 132 + ## Common Patterns 133 + 134 + ### Record with Facets 135 + ```typescript 136 + namespace com.example.post { 137 + @rec("tid") 138 + model Main { 139 + @required 140 + @maxGraphemes(300) 141 + @maxLength(3000) 142 + text: string; 143 + 144 + facets?: app.bsky.richtext.facet.Main[]; 145 + 146 + @required 147 + createdAt: datetime; 148 + } 149 + } 150 + ``` 151 + 152 + ### Open String Enum 153 + ```typescript 154 + model Card { 155 + @required 156 + section: "mainboard" | "sideboard" | "maybeboard" | string; 157 + } 158 + ``` 159 + 160 + Compiles to: 161 + ```json 162 + { 163 + "type": "string", 164 + "knownValues": ["mainboard", "sideboard", "maybeboard"] 165 + } 166 + ``` 167 + 168 + ### Token Markers 169 + ```typescript 170 + @token 171 + model ReasonSpam {} 172 + 173 + @token 174 + model ReasonViolation {} 175 + 176 + model Report { 177 + @required 178 + reason: (ReasonSpam | ReasonViolation | unknown); 179 + } 180 + ``` 181 + 182 + ## Best Practices 183 + 184 + 1. **Use open unions** - Add `| unknown` or `| string` for extensibility 185 + 2. **Prefer optional fields** - Use `?:` unless truly required 186 + 3. **10x rule** - Set `maxLength` ~10x `maxGraphemes` for UTF-8 safety 187 + 4. **Semantic namespaces** - Group by domain (`actor`, `deck`, `social`) 188 + 5. **Import externals** - Always import `"@typelex/emitter"` and `"./externals.tsp"` 189 + 6. **PascalCase models** - Converted to camelCase in JSON output 190 + 7. **Main model** - Use `model Main` for primary namespace definition 191 + 192 + ## Model Naming 193 + 194 + - `model Main` → `"main"` def (primary record) 195 + - `model Card` → `"card"` def (nested type) 196 + - Converted to camelCase in output 197 + 198 + ## Validation 199 + 200 + TypeSpec compiler catches: 201 + - Invalid references 202 + - Empty unions 203 + - Mixed union types (literals + models without proper structure) 204 + - Missing required imports 205 + 206 + Warnings for documentation issues (unescaped special chars like `@`). 207 + 208 + ## Resources 209 + 210 + - [Typelex Docs](https://tangled.org/@danabra.mov/typelex/blob/main/DOCS.md) 211 + - [AT Protocol Lexicon Guide](https://atproto.com/guides/lexicon) 212 + - [Bluesky Lexicons (reference)](https://github.com/bluesky-social/atproto/tree/main/lexicons)
+13
CLAUDE.md
··· 76 76 - **Biome**: Uses tabs for indentation, double quotes, excludes generated files 77 77 - **Devtools**: Integrated TanStack Router + Query + React devtools in root layout 78 78 79 + ## Reference Documentation 80 + 81 + Additional reference docs are in `.claude/` - **read and update these when working on relevant topics**: 82 + 83 + - **PROJECT.md** - DeckBelcher project overview, lexicon structure, and product decisions 84 + - **SCRYFALL.md** - Scryfall card API reference (IDs, fields, image handling) 85 + - **TYPELEX.md** - Typelex syntax guide (decorators, external refs, patterns) 86 + 87 + These contain important context about project decisions, API details, and tooling. Keep them updated as the project evolves. 88 + 89 + **When to create new reference docs:** If you're doing significant research, explaining complex topics repeatedly, or the user is spending time teaching you something important—create a new markdown file in `.claude/` to preserve that knowledge for future sessions. 90 + 79 91 ## Important Notes 80 92 81 93 - `src/routeTree.gen.ts` is auto-generated - never edit manually 94 + - `typelex/externals.tsp` is auto-generated from lexicons folder - add external lexicon JSON to trigger regeneration 82 95 - Demo files (prefixed with `demo`) are safe to delete 83 96 - Biome only lints files in `src/`, `.vscode/`, and root config files 84 97 - Router uses "intent" preloading by default
PROJECT.md .claude/PROJECT.md
SCRYFALL.md .claude/SCRYFALL.md
+15
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["uri", "cid"], 9 + "properties": { 10 + "uri": { "type": "string", "format": "at-uri" }, 11 + "cid": { "type": "string", "format": "cid" } 12 + } 13 + } 14 + } 15 + }
+30
lexicons/com/deckbelcher/social/like.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.deckbelcher.social.like", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "properties": { 11 + "subject": { 12 + "type": "ref", 13 + "ref": "com.atproto.repo.strongRef", 14 + "description": "Reference to the content being liked." 15 + }, 16 + "createdAt": { 17 + "type": "string", 18 + "format": "datetime", 19 + "description": "Timestamp when the like was created." 20 + } 21 + }, 22 + "required": [ 23 + "subject", 24 + "createdAt" 25 + ] 26 + }, 27 + "description": "Record declaring a 'like' of a piece of content (decklist, reply, etc)." 28 + } 29 + } 30 + }
+6 -1
typelex/externals.tsp
··· 1 1 import "@typelex/emitter"; 2 2 3 3 // Generated by typelex from ./lexicons (excluding com.deckbelcher.*) 4 - // No external lexicons found 4 + // This file is auto-generated. Do not edit manually. 5 + 6 + @external 7 + namespace com.atproto.repo.strongRef { 8 + model Main { } 9 + }
+1
typelex/main.tsp
··· 2 2 import "./externals.tsp"; 3 3 import "./richtext-facet.tsp"; 4 4 import "./deck-list.tsp"; 5 + import "./social-like.tsp"; 5 6 6 7 namespace com.deckbelcher.actor.profile { 7 8 /** A DeckBelcher user profile. */
+16
typelex/social-like.tsp
··· 1 + import "@typelex/emitter"; 2 + import "./externals.tsp"; 3 + 4 + namespace com.deckbelcher.social.like { 5 + /** Record declaring a 'like' of a piece of content (decklist, reply, etc). */ 6 + @rec("tid") 7 + model Main { 8 + /** Reference to the content being liked. */ 9 + @required 10 + subject: com.atproto.repo.strongRef.Main; 11 + 12 + /** Timestamp when the like was created. */ 13 + @required 14 + createdAt: datetime; 15 + } 16 + }