// Where each answer of a console form lives on the page, as accessible names only (role + name / label), never CSS // paths: `storebound console fill` locates every field of a section first and falls back to printed guidance when one // is missing or ambiguous (the console UI changed). Several candidate names per field absorb small wording drift. // Also the click gate (no Save / Submit / Publish / Next ... for field clicks), its scoped exceptions for moving // through a form (navigation, App Privacy's draft Save: FORM_BUTTONS) and the sign-in page detector. Pure. // The names are modeled on Apple's and Google's public help pages (sources in console-answers.ts), not recorded from a // signed-in console: the exact App Privacy dialog titles, the IARC question wording and the Play category names could // not be verified without an account, which is why every field has candidates and the engine falls back on a miss. import { AGE_GROUPS, appleName, IARC_DETAIL_SECTIONS, type ShipConfig } from './config'; import { AGE_LABELS, type Answer, APPLE_PURPOSES, type FormId, iarcSectionName } from './console-answers'; /** An accessible name: a string matches exactly, a RegExp matches the name. */ export type Name = string | RegExp; export interface FieldDef { /** Answer id. */ id: string; kind: 'text' | 'select' | 'checkbox' | 'radio'; /** text / select / checkbox: candidate names of the control. radio: candidate names of the radiogroup (optional). */ names?: Name[]; /** radio: answer value (String(value)) -> candidate names of that radio button. */ options?: Record; /** An agreement checkbox: located and highlighted, NEVER ticked (whatever the answer says). */ agreement?: boolean; /** An agreement the page's Next waits for (the IARC Terms of Use, verified 2026-10-13): the flow waits until the owner * ticked it instead of pressing a Next that does nothing. */ blocksNext?: boolean; /** radio: the options count only inside the named group (no group on the page: the field is not there). The IARC * follow-ups, whose Yes / No options would otherwise match every question of the page. */ scoped?: boolean; } export interface SectionDef { /** Shown to the owner ("page 2/3: Questionnaire"). */ title: string; /** Candidate names of the dialog the section lives in; it is present when that dialog is visible. */ dialog?: Name[]; /** A later page of a multi-page dialog: not on the page yet (none of its fields seen) is not a UI change. */ page?: boolean; /** App Privacy: the data type this section belongs to (its dialog opens from the Data Types section). */ type?: string; /** Shown only after an answer reveals it (the IARC follow-ups): never needed to finish the form. */ optional?: boolean; fields: FieldDef[]; } /** Candidate group names of the IARC yes / no follow-ups (modeled from the section structure recorded 2026-10-12). */ const DETAIL_NAMES: Record = { childlike: [/(cartoon|childlike|child-like)/i], historicalWar: [/historical|\bwar\b/i], innocents: [/innocent|defen[cs]eless/i], fierceSounds: [/sound/i], playerTrading: [/trad(e|ing)/i], block: [/\bblock/i], report: [/\breport/i], moderation: [/moderat/i], friendsOnly: [/friend/i], naziSymbols: [/nazi/i], koreaHistory: [/korea/i], terrorism: [/terror/i], crimes: [/crime/i], setting: [/setting/i], blood: [/blood/i], }; const NEVER: Name[] = [/^\u0000$/]; /** The field of one IARC follow-up answer (d:
:[: