# Arizona Institute for Autism Astro Site Static Astro + TypeScript migration of the public Arizona Institute for Autism website. ## Quick Start Run commands from this directory: ```sh cd www npm install npm run dev ``` Astro prints the local development URL, normally: ```txt http://localhost:4321 ``` The development server watches source files and refreshes the browser after edits. Stop it with `Ctrl+C`. ## Environment Setup Requirements: - Node.js 22.12 or newer (see `package.json`); the Nix shell supplies Node.js 24 - npm This repository also has a Nix development shell at the repository root: ```sh cd .. nix --extra-experimental-features "nix-command flakes" develop cd www npm install ``` ### Local Playwright environment (Linux) From the repository root, enter the opt-in browser shell: ```sh nix --extra-experimental-features "nix-command flakes" develop .#browser cd www TMPDIR=/tmp npm run build TMPDIR=/tmp node tools/playwright-smoke.mjs ``` The existing `flake.lock` pairs the Nix Playwright driver and Chromium/headless shell. `PLAYWRIGHT_MODULE` points directly to that driver; `PLAYWRIGHT_BROWSERS_PATH` selects its matching browsers. Fontconfig supplies Latin/Arabic fallback fonts. The ordinary development shell and npm dependencies remain unchanged. This browser shell is offered on Linux; the existing Darwin development shell is unchanged. Do not run `npx playwright install` or mix an unrelated npm Playwright version with these browser revisions. The smoke test starts Astro preview and Chromium in the **same execution** on an ephemeral `127.0.0.1` port. It blocks external and non-GET/HEAD requests, loads the real Spanish privacy and Library pages, verifies staging robots/title/H1, captures two full-page screenshots in a new `/tmp/aia-playwright-smoke-*` directory, and closes browser/preview. It does not submit forms or exercise production APIs. Success establishes browser transport only, not Phase 8 visual or human acceptance. Use the [Phase 8 guarded browser procedure](reports/phase-8-host-browser-verification-2026-10-14.md) for full review. Entering the Nix shell does not repair an already-running remote MCP transport or relax sandbox permissions. If the autonomous environment cannot access the Nix store, trusted certificates, sockets or browser runtime files, run the same smoke test from the host terminal. Do not disable TLS verification or bypass the sandbox. The Nix environment needs host validation before browser availability can be claimed. Copy the example environment file when testing the like/view counter or staging SEO behavior: ```sh cp .env.example .env ``` The default values are: ```txt PUBLIC_AIA_API_BASE=https://api.azinstitute4autism.com PUBLIC_ALLOW_INDEXING=false ``` The site still renders when the AIA API is unavailable. Migration and staging builds emit `noindex,nofollow` by default. `PUBLIC_ALLOW_INDEXING=true` enables production publication policy, including per-page noindex exceptions. Exercise that mode locally with `PUBLIC_ALLOW_INDEXING=true npm run build`; this does not deploy anything or change environment files. Run `npm run build` afterward to restore staging output. Only explicitly authorized production deployments should publish indexing-enabled artifacts. ## View And Build ### Development Server Use this while editing: ```sh npm run dev ``` Useful development URLs include: ```txt / /aba-therapy /autism-evaluations /learner-social-club /client-consultation /library /es /es/library /ar/library ``` ### Static Build Generate the static site with staging indexing protection: ```sh npm run build ``` The autonomous Codex sandbox denies `/etc/hosts`, so its Node runtime cannot resolve `localhost`. Use this equivalent validation command only inside that sandbox: ```sh npm run build:sandbox ``` Build output is written to: ```txt dist/ ``` ### Preview Production Output After a successful build: ```sh npm run preview ``` Astro prints the preview URL, normally `http://localhost:4321`. ### Validate Before Committing ```sh npm run build npm run audit:links npm run audit:images npm run audit:blog ``` The link audit reads `dist/` after a successful build. If rendered output is unavailable, it audits generated source routes, relative and root-relative Markdown links, and public assets instead. It writes: ```txt reports/broken-links.md ``` ## Editing Content Normal content editing happens in Markdown: ```txt src/content/pages/en src/content/pages/es src/content/pages/ar src/content/blog/en src/content/blog/es src/content/blog/ar src/content/authors/en src/content/authors/es src/content/authors/ar ``` Page and post frontmatter is validated by `src/content.config.ts`. Example: ```md --- title: "Example Article" description: "Short search and social description." slug: "example-article" canonical: "https://www.azinstitute4autism.com/library/example-article" lang: "en" translationKey: "example-article" featuredImage: "/assets/images/example.webp" alt: "Descriptive image alt text" date: "2026-06-07" author: "rula-diab" category: "Library" tags: - ABA Therapy draft: false --- Article content goes here. ``` Set `draft: true` while preparing unpublished content. ### Display Headings and Social Metadata Pages and articles may supply an optional plain-text `displayH1` in frontmatter: ```yaml title: "The existing SEO title" displayH1: "The source-supported visible heading" ``` The override changes only the visible H1. It does not change the document or social title, article cards, navigation labels, or related-post text. Empty, whitespace-only and non-string overrides fail content validation. Without an override, articles keep `title`, regular pages keep their existing service/page heading mappings and title-derived fallback, and homepage/Library components keep their existing headings. Homepage heading markup stays intact unless a plain-text override is explicitly supplied. Library template SEO titles remain unchanged. Front Matter CMS exposes the optional field for pages and blog posts. Existing featured images and page banner fallbacks supply `og:image` and `twitter:image`. Relative paths resolve against the public origin in `src/data/site.json`, never a preview host or an external canonical. Existing `alt` text supplies both image-alt metadata tags only when it describes the selected featured image; no descriptions are invented for banner fallbacks. Pages without a selected image omit image tags and retain the `summary` card; image pages retain `summary_large_image`. `og:locale` is omitted: the content languages `en`, `es` and `ar` do not establish the regional `language_TERRITORY` values required by the [Open Graph protocol](https://ogp.me/). Regional targeting remains a human localization decision. HTML language/direction and hreflang are unchanged. Run `npm run test:seo` for isolated real-Astro builds covering heading defaults, overrides, schema rejection, image metadata and shared publication invariants in staging and indexing modes. Fixtures never modify the working content or the project's `dist/`. See the dated Phase 4A section of [`reports/migration-summary.md`](reports/migration-summary.md) for source evidence. ### Publication, Canonicals, and Sitemap `src/utils/publication-policy.ts` owns route eligibility, normalized public URLs, canonical validation, robots values, and sitemap eligibility. `src/utils/publication.ts` loads the Astro collections into that manifest. `src/pages/[...slug].astro` generates every content route from eligible entries, including home and Library indexes; their existing presentation components are under `src/components/pages/`. Shared SEO and the generated sitemap use that same manifest. The route and source-link audits also reuse the policy. - `draft: true` means unpublished: no generated HTML route in either build mode. The Arabic homepage `/ar` remains a draft; published Arabic Library routes remain available. Navigation does not link to the unpublished homepage. - `noindex: true` keeps a published route accessible, emits `noindex,follow` in indexing-enabled builds, and excludes it from the sitemap. It defaults to `false`. Staging always overrides this with `noindex,nofollow`. - An omitted `canonical` gets the normalized public URL from `site.json` and its local route. A supplied local canonical must match that route. Invalid canonicals, duplicate canonical targets, and route collisions fail validation. - Verified syndicated articles use their existing `canonical` field for the external publisher URL. External-canonical routes are excluded from the sitemap even when they are not marked noindex. Do not use canonical values to create local routes or sitemap locations. - `src/pages/sitemap.xml.ts` generates `dist/sitemap.xml` on every build. Staging emits an empty sitemap and does not advertise it in `robots.txt`. Indexing-enabled builds include only eligible, indexable, self-canonical local routes. There is no hand-maintained `public/sitemap.xml`. - `npm run generate:sitemap` is a compatibility command that runs the Astro build; it no longer writes a separate public sitemap from frontmatter strings. The verified noindex exceptions are `/schedule-consultation`, `/employee-portal`, `/library/community-highlight-meet-rula-diab`, and `/library/new-aia-scottsdale-office`. The last two also retain their verified external canonicals. The dated evidence is in `reports/migration-summary.md`. Run `npm run test:publication` for policy tests and isolated builds of the actual application in staging and indexing-enabled modes. Fixtures test draft content, draft home/Library indexes, missing and external canonicals, and generated head/sitemap consistency without modifying the working site's output. The policy is TypeScript; Node audit/test commands use `--experimental-strip-types` for compatibility with the declared Node 22.12 minimum. Dependencies are unchanged. After publication changes, build and run `npm run audit:routes -- --offline` before `npm run audit:routes -- --check`. This creates a newly dated local reconciliation using saved production observations, preserving earlier evidence. Saved rendered metadata is specific to its build mode: after restoring the normal staging build, reconcile that output again before checking it. ### Links And Buttons Use normal Markdown links for links within prose: ```md Read the [ABA therapy guide](/aba-therapy) for more information. ``` Prose links render as the live site's orange inline links. Do not place an ordinary Markdown link on its own line merely to make it look like a button. Use `.mdx` and the `Button` component only for a source-verified call to action: ```mdx import Button from '../../../components/Button.astro'; ``` Available `Button` variants are `filled` (default), `outlined`, and `light`. Available sizes are `default` and `small`. A `_blank` target automatically adds `rel="noopener noreferrer"`. Normal Markdown links remain preferred for ordinary page and article content. ### Stable Heading Links Astro automatically generates an ID for each Markdown heading. In `.md` files, when a section needs a stable, editorially controlled fragment link, add an explicit ID with Sätteri heading-attribute syntax: ```md ## Accepted Insurance Carriers {#accepted-insurance-heading} ``` Link to it with the resulting fragment URL: ```md [View accepted carriers](/insurance#accepted-insurance-heading) ``` Keep explicit IDs unique within the page, lowercase, and hyphenated. Treat a published ID as permanent so inbound links do not break when heading text is edited. The installed MDX parser rejects this shorthand, independently of directive parsing. In `.mdx` files, use an explicit HTML heading instead: ```mdx