# Phrase page sets: what they share Four page sets for aac.aurasense.ai/app (the AAC Reference App, built on the open AAC standard) and for any app that reads Open Board Format (OBF): | Set | File | For | Design | |---|---|---|---| | Adult conversation | `adult.obz` | adults whose talking has changed: aphasia, ALS/MND, primary progressive aphasia, brain injury, Parkinson's | [DESIGN-adult.md](DESIGN-adult.md) | | Intent first (12, 20, 40) | `intent-12.obz`, `intent-20.obz`, `intent-40.obz` | anyone who starts from what they want to do; also a printed book a partner reads from | [DESIGN-intent.md](DESIGN-intent.md) | | Themed phrases | `themes.obz` | quick everyday messages, one 6 × 3 page per theme | [DESIGN-themes.md](DESIGN-themes.md) | | First switches and messages | `emergent.obz` | first communication and first switch use | [DESIGN-emergent.md](DESIGN-emergent.md) | This page covers what the four have in common: how they are written and built, the OBF they produce (and the `ext_aurasense_*` extensions they use or propose), how their translations were checked, their licences, how the app should offer them, and the decisions they need from the founder. The design follows the gap analysis of 2026-10-04 (`REPORT.md` on the model-library drive, Part 1 and §1.1, §2.2, §2.7, §2.10–2.12, §4 items 8, 10, 13, 14 and §5). Every word, layout and name here is ours. No proprietary page set was copied, and none is named on any board: the generator refuses a board that names one (see *Guards* below). ## Sources and build ``` aac-rs/tools/pagesets/phrases/ sources/adult.yaml intent.yaml themes.yaml emergent.yaml the sets, by hand build.py validate the sources; build the packages and catalogue review.py the two-reader translation check, round by round translations/.json every text's translation, status and evidence translations/review//round/ each round's record (see Languages) reports/sets.md symbols.md translation-status.md review aids, generated, committed LICENSE.md CC BY-SA 4.0 for the sources and every package built from them aac-rs/crates/aac-core/tests/pagesets_phrases.rs the import test ``` The packages are **generated, not committed**. The site build makes them: ```bash cd aac-rs/tools/pagesets/phrases python3 build.py --check # fail on bad sources, a non-reproducible build or stale reports python3 build.py --out "$SITE/pagesets/phrases" --hash-names ``` `--out DIR` writes `DIR/.obz` (English, with the pictures and every agreed translation), `DIR//.obz` (one language; `.f.obz` and `.m.obz` where the speaker's gender changes the words), `DIR/index.json` (the catalogue: families, sets, sizes, descriptions, licence, each file with its size, sha256 and how many of its cells are still in English, and each language's status) and `DIR/LICENSE.md`. `--hash-names` names packages `..obz` for a cache that keeps files for hours. `--check [--out DIR]` builds twice in memory and fails if the bytes differ, if `reports/` is stale, or (with `--out`) if DIR differs from a fresh build. `--reports` rewrites `reports/`; `--strings` writes `work/strings.json` for translators. The interface matches the core page sets' `tools/pagesets/build.py` (`--check`, an output directory, deterministic output), so one catalogue can list both; this generator's `index.json` is self-describing (`version`, `generator`) for that. Today a full build is 99 packages, 28 MB, of which the six English packages (with pictures) are 5 MB and each language package 100–450 KB. Python 3.11 or later with PyYAML. The build reads only the app's own symbol index (`crates/aac-core/src/symbols/index.json`) and sprites (`crates/aac-web/public/symbols/`); it never touches the network, and the same sources always give the same bytes (UUIDv5 ids, a fixed zip date, sorted output). ### The source format A source file has `set` (name, description, version, licence, which symbol sets it may draw from, the root board), a `palette` of named colours, `cells` (named cells used as `$name`), optional `frames` (cells placed on every board of the set), and `boards` (or, for the intent-first set, `sizes` and `sections`). A board is laid out by `rows`, each a list of cells from left to right, so the layout reads as it looks. A cell is: | Written as | Means | |---|---| | `"I'm tired."` | a message: shown and said | | `$yes` | the named cell `yes` | | `~` | an empty place, kept empty (SPEC §2: a blank keeps the grid) | | `{label, say}` | shown `label`, said `say` | | `{add: "I want to"}` | words added to the message without being said (a sentence starter) | | `{label, go: board}` | a link; with `say` too, the words are added and then the board opens | | `{label, do: backspace \| clear \| speak}` | a message-bar command | | `symbol: concept` | the picture: a concept of the app's symbol index (it must exist) | | `want_symbol: concept` | a picture we want but the index does not have (listed in `reports/symbols.md`) | | `fill: {words, photo, recording, hint}` | a template for the person to complete | | `seq: {lines, order, at_end, reset_after_minutes}` | each press says the next line | | `effect: sparkle \| bounce \| confetti` | a short animation with the words | | `src: universal-core` | where the words came from, if not written for this project | | `note:` | context for translators (not written into the packages) | A board may say `popup: true` (it closes after a choice), `speak_only: true` (its cells speak and never build a message), `parent:` (where Back goes; otherwise the first board that links to it) and `description:` (a note to the partner). YAML 1.1's other booleans are switched off: `yes`, `no`, `on` and `off` are words. ### Guards `build.py` refuses to build when: - a cell is outside its grid, two cells share a place, or a link leads to a board that does not exist; - a board cannot be reached from the root; - a symbol concept is not in the app's index, or not in a set the page set may use (the adult set uses pictograms only: Mulberry, then OpenMoji, no Bliss); - a sequence has fewer than two lines, names a cell that does not speak, or has an unknown order or ending; - any text names a proprietary product (`PROPRIETARY_NAMES`: PODD, TD Snap, Proloquo, LAMP, Unity, Minspeak, Gateway, WordPower, TouchChat, Lingraphica, Grid 3, BIGmack, Step-by-Step, PECS, Boardmaker, PCS, …); - any text reads as a medical claim (`CLAIM_WORDS`: diagnose, treatment, therapy, cure, heal, rehabilitate, recover, clinically, prevents, …). The sets are for wellness, research and clinical-trial use; they are not a medical device. ## The OBF the generator writes Each package is a `.obz`: a zip, stored without compression (what the app's own importer reads directly), of `manifest.json`, `boards/.obf` and, in the English package, `images/.svg`. Ids are UUIDv5 of the set, board and position, so a rebuilt board keeps its ids, and every language's package has the same board and cell ids in the same places (the test checks this: changing language never moves a word, SPEC §3). Standard OBF fields: `format`, `id`, `locale`, `name`, `description_html`, `license`, `grid` (`rows`, `columns`, `order`), `buttons` (`id`, `label`, `vocalization`, `label_locale`, `vocalization_locale`, `load_board`, `action`, `image_id`, `background_color`, `border_color`, `translations`), `images` (`id`, `content_type`, `width`, `height`, `path`, `symbol`, `license`) and `sounds`. ### Extensions the app already reads or writes | Field | Where | Meaning | |---|---|---| | `ext_aurasense_provenance` | board, button | where the words came from: `source_label` / `source_vocalization` (the stored words), `words_locale` (the words are in this language, kept exactly as written, never glossed: the app's own-words cell), `translation` (`"agreed by two model readers; native review pending"`, or `"pending: shown and said in English"`), `english` (the English source), `source` (file, board and position in the sources), `symbol_wanted`, `words_source` | | `ext_aurasense_locked` | board, button | `false`: the person may change everything | | `ext_aurasense_text_color` | button | the text colour | | `ext_aurasense_action: "buffer"` | button | add the words to the message without saying them (sentence starters) | | `ext_aurasense_symbol` | button | the picture, as a concept of the symbol index (the app draws it in the person's chosen set and skin tone; Track B reads and writes it) | | `images[].symbol` + `license` | image | the symbol set and file, with the set's licence, attribution and conditions | ### Extensions these sets add A client that does not know them still works: each falls back to plain OBF. The **App** column is the state on `aac/pagesets-phrases`: *acts on it* (`aac_core::behaviours`, `aac-web` `sequences.rs`, `behaviours.rs`, `actions.rs`, `scan.rs`, `switches.rs`, `pointing.rs`), or *kept* (on the cell or board, written back on export, and named once in the import's warning: "These boards use features this version can't do yet: … They are kept."). | Field | Where | What the app does | Fallback | App | |---|---|---|---|---| | `ext_aurasense_sequence` | button | each press says the next line; full behaviour in [DESIGN-emergent.md](DESIGN-emergent.md#the-sequence-extension) | `vocalization` is the first line | acts on it | | `ext_aurasense_effect` | button | a short visual effect with the words (DESIGN-emergent.md) | none | kept ("animations") | | `ext_aurasense_behaviour.after_choice: "return"` | board | a popup: after a cell that says or adds words, go back to the board that opened this one | stays; a Back or Start cell is on the page | acts on it | | `ext_aurasense_behaviour.message: "speak_only"` | board | its cells speak at once and are never added to the message | words are added too | acts on it | | `ext_aurasense_link_says: "I want"` | button with `load_board` | add these words to the message as a speaking cell would, then open the board (the words themselves, as well as in `vocalization`: a reader that makes the button a link keeps no vocalization, but keeps this field) | the link opens; the words are not added | acts on it | | `ext_aurasense_fill_in` | button | `{words, photo, recording, hint}`: a template cell; Edit mode marks it "to fill in" and lists every such cell after the set is added; the hint is shown in the cell editor; editing the cell drops the mark | the cell says its words (`…` is a pause) | kept ("cells to fill in") | | `ext_aurasense_book.page` | board | page number in a printed book | printing numbers pages itself | kept quietly (the app prints books and numbers them itself) | | `ext_aurasense_book_goto` | button with `load_board` | the page it links to, printed as "→ 12" | none | kept quietly (same) | | `ext_aurasense_speaker_forms` | button, in a language's f/m packages | `{f: {label, vocalization, lines}, m: {…}}` for a word that changes with who says it: the app puts the person's form there, or leaves the word out while they would rather not say | the package's own form | acts on it | | `translations..ext_aurasense_speaker_forms` | button | `{f: {label, vocalization}, m: {…}}` where the speaker's gender changes the words | that language's words are absent: English | | `translations..ext_aurasense_sequence_lines` | button | a sequence's lines in that language | English lines | | `manifest.ext_aurasense_page_set` | manifest | `key`, `name`, `version`, `locale`, `speaker_forms` | none | **The importer defect, fixed.** `aac_core::obf` used to accept every `ext_aurasense_*` field silently and keep none it did not know, so a sequence or popup was lost without a word (SPEC §8 forbids it). It now keeps every field it does not map — `ext_aurasense_*` or any other — on `Cell::extra` / `Board::extra`, writes them back on export, and adds one warning per import naming, in plain words, the features it keeps but does not act on. A few fields with no meaning for the app (`url`, `data_url`, `description`, `description_html`, `default_layout`, `default_locale`, `background`, and the position fields `top`, `left`, `width`, `height`) are kept without a warning. The test checks the warning, and that sequences, links that say words and board behaviours survive an import. ### SPEC text (draft, after 0.2.0) Approved and now in `aac/SPEC.md` (§2.2, §2.3, §2.4, §5.9 and the §8 addition, with the summary under "Proposed (draft, after 0.2.0)"): > **§2.2 Sequences (proposed).** A cell **MAY** carry a sequence: an ordered > list of two or more lines, an order (`in_order` or `shuffle`), what > happens after the last line (`start_again` or `stay`), whether the lines > are added to the message (default: not), and an optional idle time after > which it starts again. Each activation of the cell says the next line. A > line **MAY** name another cell of the same board, whose current words (and > recording) it then says. Where the next line is kept is the person's > state, not the board's: using a sequence **MUST NOT** change the board. > A client that cannot play a sequence **MUST** say the cell's own words > (its first line) and **MUST** keep the sequence when it saves the board. > Undo after a line **MUST** step the sequence back. A press during a line > **MUST** be honoured (the person sets the pace); the hold and release > times of §5 still apply. > > **§2.3 Board behaviours (proposed).** A board **MAY** declare that it > closes after a choice (a popup) or that it speaks without building a > message. After a cell of a popup that says or adds words is chosen, a > client **MUST** return to the board that opened it (a popup's continuation > pages count as the popup). The person **MUST** be able to turn popups off > for all boards; the choice travels with their settings. This is a > property of the vocabulary's design and returns to the board that opened > the popup, not home: it is not the "go home after each word" option of > §5.6 item 6, which stays the person's alone and, where they have chosen > it, wins over a popup's return. > > **§2.4 Links that say words (proposed).** A cell that goes to another > board **MAY** also carry words; a client **MUST** add them to the message > as a speaking cell would, then open the board. > > **§5.9 One target (proposed).** When a scan has a single target — a > one-cell board, with the bar and the navigation not scanned (§5.5, §5.6 > let the person choose that) — every press of a switch **MUST** select it > at once, and a touch or dwell anywhere on the board **MUST** select it. > > **§8 addition (proposed).** An extension field a reader does not act on > **MUST** be kept with the board or cell (§2) and reported once as a > warning, so that a sequence, popup or other behaviour is never lost > without a word. ## Symbols Pictures are chosen by concept from the app's own symbol index (472 concepts on 2026-10-05: Mulberry, OpenMoji, Bliss, all CC BY-SA 4.0), never by inventing ids. Each set says which sets it may draw from: all four use Mulberry, then OpenMoji (pictograms); none uses Bliss, which is a semantic symbol language rather than a picture, and which a person can still choose in the app (the app draws `ext_aurasense_symbol` in the person's own set). Many adult concepts have no symbol yet (throat, chest, suction, frustrated, appointment, …): those cells show their words alone, and an adult may add a photo instead. `reports/symbols.md` lists every concept wanted and where. Adding them is a job for `tools/symbols` (a confident match only: a wrong picture is worse than none), after which the sources name them with `symbol:`. The English packages carry the pictures (each symbol once, as `images/.svg`, cut from the app's sprite with its set's attribution); the language packages name each symbol by concept only, which the app draws from its own sprites. That keeps each language package small (100–450 KB); a language package opened in another OBF app shows words without pictures. `build.py` can embed pictures in every package if that is wanted (one condition in `Package.button`). ## Languages English is the source. A translation is used only where **two independent readers agree**: 1. **Draft.** A drafter (reader 1) translates each text, seeing the English and where and how it is used (`review.py prepare`): what a native speaker would say, short and spoken; polite address in the adult set, familiar in the emergent set; a wording that does not mark the speaker's gender wherever one is natural, and otherwise both forms (`f`, `m`) — never slash forms. 2. **Blind back-translation.** A second reader, who never sees the English (`review.py blind` writes only the translations, with each text's role), translates the drafter's words back into English and says how they read: grammar, register, and whose gender they mark. 3. **Judge.** The English, the translation and the back-translation are compared (`review.py judge`); "agree" only when the back-translation means what the English means, with nothing added or lost, in natural words. 4. **Merge.** Agreed texts become `"agreed"`; the rest `"disputed"`, with the reason (`review.py merge`). A disputed text goes to a second round: the drafter revises it with the reason, and a fresh blind reader checks again. What is still disputed stays in English. The status of every text in every language is data: `translations/.json` (`status`, `round`, `text` or `forms`, `back_translation`, `notes`), summarised in `reports/translation-status.md` and in each build's `index.json`. Each round's record is kept under `translations/review//round/`: `draft-input.json` (what the drafter saw), `draft.json`, `blind-input.json` (what the blind reader saw: no English), `back.json`, `verdict.json`. Every reader was an AI model, so every agreed text says **"agreed by two model readers; native review pending"**, in the translation files and in the packages' provenance. A native speaker's review is still wanted for every text. **Result (2026-10-05).** 1,075 English texts; three rounds. | | es | fr | de | pt | it | zh | ja | ar | hi | ru | |---|---|---|---|---|---|---|---|---|---|---| | Agreed | 1,075 | 1,075 | 1,075 | 1,075 | 1,075 | 1,075 | 1,074 | 1,074 | 1,075 | 1,075 | | Disputed in round 1 | 10 | 17 | 9 | 9 | 6 | 15 | 29 | 21 | 11 | 6 | | Texts with speaker forms | 29 | 20 | 0 | 30 | 24 | 0 | 0 | 0 | 14 | 15 | Still in English after round 3: Japanese "OK" (a feelings word; every wording offered could be heard as "so-so" or a polite "no thanks") and Arabic "I did that for … years." (one cell of the adult set's work page). What round 1 caught, among others: Spanish "Eso me pone" for the starter "That makes me" (sexual slang in Spain), "Salir" for "Going out" (reads as "Exit"), "Está demasiado alto" for "It's too loud" ("too high"); Hindi कल for both "yesterday" and "tomorrow"; Japanese requests that could be heard as about the listener ("brush/wash my…"); Russian «Мне очень неудобно» for "I'm very uncomfortable" (usually "I'm embarrassed"); Arabic الحمام unvoweled (bathroom or pigeons). The readers also showed three English texts were unclear in every language, and they were changed: "Adult home" (a care home) became "Home"; "My own card" (an ID card) became "In my own words"; three theme starters that no word on their page completed were rewritten. Two blind readers (Spanish, Arabic) were stopped part-way by the model provider's content filter; Spanish was rerun as it was, Arabic as four readers of one part each. Neither saw the English. A cell is shown and said in one language: if any of its texts (label, message, sequence lines) is not agreed, the whole cell stays in English (an empty translation is allowed only for a link's spoken words that the language does not need, such as the English "to"; a label is never blank), marked `label_locale: "en"` / `vocalization_locale: "en"`, and its board's `description_html` carries a visible note, in the board's language: "Some messages on this board are still in English: their translation has not yet been agreed by two readers." Done first: the ten largest app languages (es, fr, de, pt, it, zh, ja, ar, hi, ru). **Pending** (English only, listed in `index.json`): nl, ko, he, ur, fa, bn, ta, te, ml, mr, kn, gu, pa, th, vi, id, ms, tl, pl, uk, ro, cs, sv, da, nb, fi, sw, am, ha, yo, zu, tr, el, hu. Adding one: add it to `LANGUAGES` in `build.py` and `LANGUAGE_NOTES` in `review.py`, run the four steps (`review.py prepare/blind/judge/merge`), and build. **What translation cannot fix.** Sentence starters and words are combined in the order the person picks them ("I want" + "to eat" + "an apple"). In languages whose word order differs — Japanese and Hindi put the verb last, German often does — the combined message is telegraphic; each language's drafter shaped the fragments to survive it (DESIGN-intent.md lists how). Phrase cells (whole messages) do not have the problem. Where a language marks a referent's gender in a label ("Friend", "Doctor", "Nurse" on the People templates), the drafters used the usual default and noted it: those are templates the person fills in and may rename. ## Licences - The page sets (words, layouts, packages): **CC BY-SA 4.0**, author "AuraSense Tech Corporation and the open-standards AAC contributors" (`tools/pagesets/phrases/LICENSE.md`, copied into every build). ShareAlike because the English packages embed CC BY-SA symbols, and so that improvements stay open. - Symbols: Mulberry Symbols, OpenMoji, CC BY-SA 4.0, each image carrying its set's licence, attribution and conditions (Mulberry's symbols must stay free of charge). - Words: written for this project unless a cell says `src:`. The only borrowed list is the Universal Core vocabulary (21 single words on the 40-cell intent-first start page; CC BY 4.0 per UNC CLDS, CC BY-SA 4.0 per Project Core: treated as BY-SA, words only, no vendor symbols). - Nothing from PCS, SymbolStix, Widgit, METACOM, ARASAAC or Sclera; no proprietary vocabulary's words, positions, wording or names (§5 of the report). The design notes name products only to say what was not copied. ## How the app should offer them **What is built on this branch.** The app acts on the sets' behaviours: sequences (by touch, switch scanning with one or two switches, pointer and gaze dwell; "2 of 4" shown; Undo on the message bar's Delete; Modeling shows the next line without moving on; the partner menu's "Start the lines again" and "Back one line"; each line can be recorded in the person's own voice from the cell editor, through message banking), popups that close after a choice, speak-only boards, links that say words, one-target pages (§5.9), a large scan highlight, and the profile setting "Words about myself" (`AacProfile::speaker_form`; Settings → Language, shown for es, fr, pt, it, hi and ru only, never assumed). The place in each sequence is the person's (`localStorage` `aac_sequence_places`, per profile), never in the board. The English cells of a phrase package — the English package taken for a language without its own, and the cells still in English inside a language's package — are marked as English words written as they are (`lang` `"en"`, `pagesets::prepare`): shown in English, marked, said by an English voice, never glossed word by word and never hidden. "Words about myself" can be declined ("I'd rather not say"): each word that changes with who says it is then left out, hidden in place and marked in Edit mode "Write this your way", until a form is chosen in Settings, when those words appear in it. A board's description (its note to the people around the person: what it is for, that nothing is sent, that it does not replace alarms or monitoring) is shown as a line above the board. Pictures: when a person exports boards for another app, the export already embeds each cell's symbol as a `data:` URI from the chosen set's sprite (`Cell::symbol` → `symbols::symbol_for_cell` → `ObfExportOptions::sprites`), so nothing more was needed there. **In the shared chooser.** The sets are offered by the core page-set catalogue and chooser (`tools/pagesets/catalog.py`, `aac_core::pagesets`, `aac-web` `pagesets.rs` and `catalogue_sets.rs`), beside Growing Core and Fixed Paths, at setup ("Words to start with") and in Settings → Words and boards. There is no second chooser. 1. **The generator.** `build.py --out DIR` writes, beside the packages, `DIR/families.json` in the catalogue's generator contract: four families (`phrases-adult`, `phrases-intent`, `phrases-themes`, `phrases-emergent`, `chooser: "phrases"`), one package per set, language and speaker form (99), each with `lang`, `gender` (`"f"` / `"m"` / null), `size` (`"12"`, `"20"`, `"40"` for intent first, else `""`), `rows`, `cols`, `home`, `label`, `description`, `boards`, `cells` and `cells_in_english`; `languages[code]` is `GlossOnly` with the share agreed (a translation of English phrases is a gloss, SPEC §4.1, however good). `tools/pagesets/generators.json` lists it, and `data/pagesets/index.json` (the catalogue compiled into the app) lists all six families, 118 packages; `catalog.py --check` fails a build whose packages differ. The site's build puts the packages in `/app/models/pagesets/`. 2. **By language and speaker form.** The chooser takes the package with `Family::for_person(size, variant, locale, speaker_form)`: the person's language (exact tag, then base), in their form where that language's packages differ by it, else the English one. Where a set's words in the person's language change with who says them (`Family:: speaker_form_matters`) and `AacProfile::speaker_form` is not set, setup shows "Words about myself" with the set and will not start until it is answered; Settings asks it when Add is pressed and adds the set once it is answered. Never assumed; the answer is kept on the profile and can be changed in Settings → Language, which shows the question only for a language where some set's words differ (`phrase_sets:: speaker_form_matters`, from the catalogue). 3. **How far the language goes.** Each family says what it is, its sources and licence, and, for a language other than English, its `language_note` and, for the chosen size, "All of it is in Spanish." or "1 of its 366 cells is still in English, and says so." from the package's `cells_in_english`; a language with no package of its own gets the note alone (in English for now). 4. **Adding.** `pagesets::add` / `start_with` → `prepare` → `obf::import_obz`, `give_new_ids`, `editing::add_boards_as_page_set`; the import's warning is shown with the result (each package keeps, and names, page numbers for a printed book; all but Themed phrases, cells to fill in; First switches, animations). Each board keeps its licence (CC BY-SA), so re-sharing keeps the attribution. 5. **After adding.** If the set has `ext_aurasense_fill_in` cells (adult: names, photos, scripts, own voice; emergent: own message, news, photo choices), open Edit mode's list of them: "Fill in your details". For the adult set, offer later "Make *Simple page* my home" (the staged plan in DESIGN-adult.md). 6. **Setup.** The setup wizard can offer the matching set by the path the person picks: an adult whose talking has changed → Adult conversation; a first switch user → First switches and messages; someone starting out → Intent first (12); a reader who wants quick phrases → Themed phrases. 7. **Behaviours.** Built (see above), except fill-in badges and effects, which are kept and named in the import's warning. In a client without them the sets still work: sequences say their first line, popups stay open (every page has Back or Start), speak-only boards also add to the message, links that carry words only open the page. Track A's per-person setting "After a word on another board, go back to Home" (`AccessPrefs::return_home`) wins over a popup's return to its opener; the person can also keep popups open (`AccessPrefs::popups_close`, Access settings → Going back). ## Tests `crates/aac-core/tests/pagesets_phrases.rs` runs `build.py --out` into Cargo's per-test temporary directory (it needs Python 3.11+ with PyYAML; `AAC_PYTHON` names the interpreter), then checks every package in the catalogue, English and every language and speaker form: - the catalogue lists all six sets in English and ten languages, its files match the directory, and its sizes are the files'; - every package validates with the independent `open_board_format` crate (no errors), and imports with the app's own `obf::import_obz` with no warnings, every board present and the root first; - every cell sits inside its grid, one per place; every link lands on a board of the package; every board is reachable from the root; - every `ext_aurasense_symbol` is a concept of the app's symbol index; every picture names a symbol its set has, is in the package, and carries its CC BY-SA licence; - every language's package has the same boards with the same cell ids in the same places as the English one; - every cell of a language package is either wholly in that language (and says it was agreed by two readers) or marked as English, and a board with English left on it says so; - imported with the app's importer, a translated cell is the person's own words in that language (`Cell::lang`), an English cell is an English key, and every chosen symbol is kept (`Cell::symbol`); - sequences have two lines or more, valid order and ending, lines that name cells of the same board, and say their first line to a client without the extension; board behaviours are only the two defined; - in the intent-first sets, each operational command (Oops, I'm finished, Back to the start, Go back, Turn the page) is in one place on every page. `build.py --check` adds: the sources are valid (every guard above), two builds are byte-identical, and `reports/` is current. ## Decisions for the founder 1. **Speaker gender.** *Decided:* "Words about myself: feminine / masculine", shown only for es, fr, pt, it, hi, ru, asked when a set in one of them is added, no default, changeable in Settings. Built. 2. **Address.** Adult set: polite address (usted, vous, Sie, Lei, Вы, です/ます); emergent set: familiar; intent and themes: familiar with family and helpers. An informal adult variant (to family) would double the adult set's translations; not proposed now. 3. **Pictures in language packages.** *Decided:* language packages name symbols; the app embeds the pictures when a person exports boards for another app (it already does). 4. **The importer defect.** *Decided and fixed* (above). 5. **The SPEC text.** *Approved* as draft text after 0.2.0; in SPEC.md. 6. **Native review.** Every translation is model-read only. Which languages get native reviewers first (the AAC site's largest audiences), and who. 7. **Name collision.** *Decided.* TD Snap has an "Express" page set, and the app was previously called Express too; it is now the AAC Reference App. Our page set is called "Themed phrases", and nothing here uses the word.