From
bafyreigaixd5iwr5bjs2mrtgmpfs2i7as63hez5b5rce6jbgp6jb6r3ygq
indexed 2026-09-16 19:18 UTC
View this version
To
bafyreifyevncz6ko6kifbdq77dgdjqxtfpcndqyztgdtdrbkxcemcyve5y
indexed 2026-10-15 09:08 UTC
View this version
Compatibility
backward compatible Backward compatible. 2 non-breaking changes.
Non-breaking changes (2)
- AddedVertex AddedVertex { vertex_id: "at.kotoba.lesson.pack#item.ruby" }
- AddedEdge AddedEdge { src: "at.kotoba.lesson.pack#item", tgt: "at.kotoba.lesson.pack#item.ruby", kind: "prop", name: Some("ruby") }
Migration guidance
Added
AddedVertex { vertex_id: "at.kotoba.lesson.pack#item.ruby" }
Notes
- Non-breaking: AddedEdge { src: "at.kotoba.lesson.pack#item", tgt: "at.kotoba.lesson.pack#item.ruby", kind: "prop", name: Some("ruby") }
Changes
11
{22
"$type": "com.atproto.lexicon.schema",33
"defs": {44
"defaults": {55
"description": "Pack-level drill defaults (SPEC §4). Open additional keys round-trip via the pack `meta`.",66
"properties": {77
"katakanaMultiplier": {88
"description": "Queue weight multiplier for katakana. Fixed-point ×10000 (e.g. 1.5 -> 15000); no float type.",99
"type": "integer"1010
},1111
"tapToTypeBox": {1212
"description": "Leitner box at/above which a glyph may graduate tap -> type.",1313
"maximum": 5,1414
"minimum": 1,1515
"type": "integer"1616
}1717
},1818
"type": "object"1919
},2020
"forms": {2121
"description": "Authored conjugation surfaces, keyed by the form names in src/drill/conjugate.ts. Kana only — the kanji surface is derived from the item's `kanji` form. A key this build does not know is ignored, so a form added later needs no lexicon change.",2222
"properties": {2323
"chau": {2424
"type": "string"2525
},2626
"imperative": {2727
"type": "string"2828
},2929
"masu": {3030
"type": "string"3131
},3232
"nai": {3333
"type": "string"3434
},3535
"nakatta": {3636
"type": "string"3737
},3838
"nakya": {3939
"type": "string"4040
},4141
"potential": {4242
"type": "string"4343
},4444
"ta": {4545
"type": "string"4646
},4747
"te": {4848
"type": "string"4949
},5050
"teru": {5151
"type": "string"5252
},5353
"toku": {5454
"type": "string"5555
},5656
"volitional": {5757
"type": "string"5858
}5959
},6060
"type": "object"6161
},6262
"item": {6363
"description": "A card. A discriminated union on `type` (#1), flattened into one object because lexicons have no oneOf: a RECOGNITION item (hiragana | katakana | kanji | word) requires glyph + romaji, a GRAMMAR item (particle | contraction | usage | sentence) requires id + prompt + answer. `conjugation` is a RECOGNITION type (#146): a derived verb surface, so it carries glyph + romaji like any word. Only `type` can be required here; the per-branch requiredness is enforced by lessons/pack.schema.json (JSON Schema if/then) and src/packs/validate.ts, which is what every reader of this record already routes through.",6464
"properties": {6565
"accept": {6666
"description": "GRAMMAR branch, optional. Alternative answers graded correct beside `answer` — casual forms routinely have two natural ones. Exact-match grading is unchanged; the accepted set just has more members.",6767
"items": {6868
"type": "string"6969
},7070
"type": "array"7171
},7272
"answer": {7373
"description": "GRAMMAR branch, required there. The one canonical answer; graded by exact compare after trim.",7474
"type": "string"7575
},7676
"choices": {7777
"description": "GRAMMAR branch, optional. Authored options for the choice grid (#145); contains `answer` when present. Never empty — the app drops an empty or wrong-typed list at the upload boundary rather than writing one.",7878
"items": {7979
"type": "string"8080
},8181
"type": "array"8282
},8383
"confusableWith": {8484
"description": "ROMAJI (not glyphs) of same-script confusers; cold-start distractor seed.",8585
"items": {8686
"type": "string"8787
},8888
"type": "array"8989
},9090
"forms": {9191
"description": "Authored kana surfaces for the forms no rule derives (#136) — an irregular's whole paradigm, or one form of an otherwise regular verb (iku's itte). A form with no entry falls back to the derivation.",9292
"ref": "#forms",9393
"type": "ref"9494
},9595
"gloss": {9696
"description": "English meaning; recommended for words/kanji, and for a grammar item it is the meaning of the whole line.",9797
"type": "string"9898
},9999
"glyph": {100100
"description": "RECOGNITION branch, required there. 1+ chars: single kana, word, or kanji.",101101
"type": "string"102102
},103103
"group": {104104
"description": "Verb conjugation class (#136): 1 godan, 2 ichidan, 3 irregular. Word items only. The app derives every form from it (src/drill/conjugate.ts) instead of the pack authoring them.",105105
"maximum": 3,106106
"minimum": 1,107107
"type": "integer"108108
},109109
"id": {110110
"description": "GRAMMAR branch, required there and unique within the pack. The SRS state map is glyph-keyed and a grammar item has no glyph, so it is keyed `grammar:<id>` (#145). Uniqueness is enforced by validate.ts, not here.",111111
"type": "string"112112
},113113
"kanji": {114114
"description": "Written form in kanji (or kanji + okurigana) of a kana word — にほんじん -> 日本人. The below-hint path is passive (#125); a usable ruby meaning prompt records the separate kanji-recognition lane (#171). Omit where there is no kanji form (gairaigo) or the word is normally written in kana.",115115
"type": "string"116116
},117117
"meta": {118118
"description": "Item-level open extension; UI ignores, round-trips.",119119
"type": "unknown"120120
},121121
"prompt": {122122
"description": "GRAMMAR branch, required there. The sentence, with the blank written by the author (U+FF3F). Opaque to this layer: it may carry `{kanji|kana}` ruby markup (#139).",123123
"type": "string"124124
},125125
"romaji": {126126
"description": "RECOGNITION branch, required there. Expected reading; lowercase Hepburn, exact case-insensitive match.",127127
"type": "string"128128
},129129
"row": {130130
"description": "Gojuon row label, single kana and combination kana (yoon + extended) only.",131131
"type": "string"132132
},133
"ruby": {134
"description": "Optional authored furigana boundaries for a recognition item, using `{base|reading}` markup. Used only when it reconstructs the plain `kanji` form and `glyph` reading; unusable markup falls back safely.",135
"type": "string"136
},133137
"type": {134138
"description": "Open. Recognition: hiragana | katakana | kanji | word | conjugation (#146, a derived verb surface). Grammar (#1): particle | contraction | usage | sentence.",135139
"type": "string"136140
},137141
"weight": {138142
"description": "Optional author priority override. Fixed-point ×10000 (e.g. 1.8 -> 18000); lexicons have no float type. App scales at the record edge (see README).",139143
"type": "integer"140144
}141145
},142146
"required": [143147
"type"144148
],145149
"type": "object"146150
},147151
"main": {148152
"description": "Lesson content the app drills. Append-only stack; latest = active. Free-form by design: a known core the UI reads + open `meta`/`defaults` round-tripped untouched (SPEC §3.1a). This record body IS the upload interchange shape (lessons/pack.schema.json).",149153
"key": "tid",150154
"record": {151155
"properties": {152156
"createdAt": {153157
"description": "ISO 8601. NOTE: the app currently stamps a date-only string (YYYY-MM-DD), not a datetime — left unformatted here pending reconciliation (see README).",154158
"type": "string"155159
},156160
"defaults": {157161
"ref": "#defaults",158162
"type": "ref"159163
},160164
"items": {161165
"items": {162166
"ref": "#item",163167
"type": "ref"164168
},165169
"type": "array"166170
},167171
"meta": {168172
"description": "Pack-level open extension; UI ignores, round-trips.",169173
"type": "unknown"170174
},171175
"phase": {172176
"description": "Open. kana | vocab | grammar.",173177
"type": "string"174178
},175179
"rationale": {176180
"type": "string"177181
},178182
"script": {179183
"description": "Declared scripts; drives chips + Focus. The scripts the glyphs are WRITTEN in — for a `word` item that is its kana script, not \"word\" (#99). Derived the same way if omitted; a word that is unsegmentable or mixes both kana falls back to \"word\". A declared script is cleaned by the app (#99): \"word\", junk and duplicates are dropped and the rest kept; \"word\" returns only if derivation finds an item with no derivable script.",180184
"items": {181185
"type": "string"182186
},183187
"type": "array"184188
},185189
"title": {186190
"type": "string"187191
}188192
},189193
"required": [190194
"title",191195
"items"192196
],193197
"type": "object"194198
},195199
"type": "record"196200
}197201
},198202
"id": "at.kotoba.lesson.pack",199203
"lexicon": 1200204
}