The source for the App Fair Project website: a static site built with
Astro and deployed to GitHub Pages by .github/workflows/.
Follow STYLE.md for project naming and site prose.
Requires Node 22.12 or newer.
npm install
npm run dev # http://localhost:4321, with hot reload
npm run build # production build into dist/, then the Pagefind search index
npm run preview # serve dist/ (search only works here and in production, not in dev)astro.config.mjs site URL, i18n routing, markdown pipeline
src/
├── content/
│ ├── blog/ blog posts (markdown; the file name is the URL slug)
│ └── pages/ standalone pages, served at /<path>/ (marketplace/, imprint, privacy, …)
├── content.config.ts the two collections' frontmatter schemas
├── i18n/ en.json and one JSON table per locale, plus the t() helper
├── layouts/ BaseLayout: <head>, nav, footer, theme + design bootstrap
├── components/ Nav, Footer, ThemeToggle, LangSwitcher, Search, HeroVisual, …
├── pages/ routes: index, blog/, blog/[slug], blog/rss.xml, [...slug], og/, 404
├── styles/ global.css (system), themes.css (styles × light/dark), layouts.css
└── lib/ site constants (URLs, social links, stores), post helpers
public/ static assets (icons, store badges, images, the App Index schema)
Every string in the site chrome and on the homepage is looked up by key from src/i18n/<locale>.json,
falling back to en.json for any key a locale has not translated. To translate more of the site,
add keys to the locale file; to add a locale, add it to src/i18n/locales.mjs and create its JSON.
Locale routes (/fr/…, /de/…, …) exist for every page. Pages without a translated copy — the blog
and the policy pages today — render their English content under the locale prefix with localized
chrome (Astro's i18n fallback with fallbackType: 'rewrite'). A translated page would be a
localized copy of the markdown file; wiring that up is the next step when translations arrive.
The site's design is Midnight (style) + Split (layout) + Catalog (hero graphic), set as design
in src/lib/site.ts. In npm run dev a Design button in the bottom-right corner switches between the
alternative styles, layouts and graphics that were built for comparison. It sets data-style /
data-layout / data-visual on <html>, remembers the choice in localStorage, and honours
?style=…&layout=…&visual=…&theme=… in the URL. Production builds (npm run build, and so the deployed
site) render neither the picker nor its bootstrap script, and HeroVisual.astro emits only the chosen
graphic, so they always show the default. In dev, ?popup=free|noAds|openSource|noTracking opens one of
the graphic's dialogs on load, for reviewing it without a pointer.
To retire the alternatives: delete src/components/DesignPicker.astro, its two showPicker includes in
BaseLayout.astro, designOptions in src/lib/site.ts, the non-default variants in HeroVisual.astro,
and the unused blocks in themes.css and layouts.css.
Pagefind indexes dist/ after the build (npm run build). Only
default-locale pages carry data-pagefind-body, and the index is forced to one language, so a
visitor on any locale searches the whole (English) site.
The App Fair Project © 2026 by the App Fair is licensed under CC BY-SA 4.0.
src/content/pages/docs/ supplies /docs/, linked as Developers in the header. MDX pages
embed SubmissionFlow.astro (linked SVG) and SubmissionStep.astro. The checklist's command
generation, input validation and versioned local-storage parsing live in src/lib/submission.mjs.
Run npm test for their regression tests. No commands are executed by the checklist.
SubmissionGuide.astro renders a normal single-column document. Each SubmissionStep.astro
contains one numbered heading, explanatory prose, and its actions and reference links. The same
step component renders the personalized commands. The overview and section anchors use native page
scrolling; there are no nested scrolling panes or synchronization handlers.
SubmissionForm.astro initializes submission-checklist.ts, which updates commands, validates
input and saves app details. SubmissionValue.astro updates prose identities through
submission-state.ts and submission-values.mjs. Commands live in submission.mjs; keep guide
step IDs matched to stepIds. The obsolete /docs/checklist/ route has been removed.
The policy pages are /app-rules/ for users and /docs/inclusion-criteria/ for developers. Keep their requirements aligned; the developer page holds submission details.
Docs use the normal content collection, localized fallback routes, sitemap and Pagefind build.
Use npm run build followed by npm run preview to test search; Astro dev does not build its index.
When changing the submission process, check the App Fair template and catalog workflow before
updating command examples. Generated checklist commands assume a personal catalog fork.
With Playwright available, run the end-to-end checks against the preview server:
SITE_URL=http://127.0.0.1:4323 node tests/guide.browser.mjs
SITE_URL=http://127.0.0.1:4323 node tests/instruction-tabs.browser.mjs
SITE_URL=http://127.0.0.1:4323 node tests/releases.browser.mjs
SITE_URL=http://127.0.0.1:4323 node tests/policies.browser.mjs
# Set PLAYWRIGHT_MODULE to a package path if Playwright is installed outside this checkout.Use GitHub-style alerts in Markdown or MDX. The shared renderer supplies a label, icon and light/dark colors. Ordinary blockquotes remain quotations.
> [!INFO] iOS testing in CI
> GitHub CI can run iOS tests and capture screenshots.Supported types: NOTE (context), INFO (platform facts), TIP (shortcuts), IMPORTANT
(requirements), WARNING (likely mistakes), and CAUTION (irreversible changes). The optional
title is plain text. The body supports normal Markdown, including links, lists and code blocks.
Use callouts for exceptions and supporting advice; keep the main instructions in the guide.
For Astro components or interactive MDX, use the equivalent component:
import Admonition from '../components/Admonition.astro';
<Admonition kind="info" title="iOS testing in CI" id="ios-ci-note">
GitHub CI can run iOS tests and capture screenshots.
</Admonition>src/lib/admonitions.mjs defines the variants and icons. rehype-admonitions.mjs renders
Markdown alerts; Admonition.astro renders component callouts. Both use the site-wide styles in
src/styles/admonitions.css. Static callouts use role="note", not an interrupting live alert.
Markdown fenced code blocks automatically get a floating copy button. In Astro or MDX,
use CodeBlock.astro for generated examples:
import CodeBlock from './components/CodeBlock.astro';
<CodeBlock code="day launch" label="Copy launch command" />CodeCopy.astro is included by BaseLayout.astro; it shares the button template, styles
and clipboard handler across both forms. Code remains selectable without JavaScript.
A successful copy shows a checkmark and “Copied” for two seconds. Clipboard failure
selects the text and shows manual-copy instructions instead. Copying reads the current
code text, including edits to generated commands, and preserves whitespace. Add
data-no-copy to a pre or an ancestor to opt out. Inline code is unaffected.
The app token field checks https://api.github.com/users/{token} after 600 ms without an
edit. This unauthenticated endpoint resolves both users and organizations and supports
CORS.
Green means a 404 (apparently available), red means an existing account, blue means checking,
and amber means the check failed. GitHub can still reserve or reject a name at creation.
Results are cached in memory for five minutes; obsolete requests are aborted and ignored.
Checks time out after eight seconds and pause on API rate limits. They never block the guide
or request credentials. Invalid tokens do not trigger requests.
InstructionTabs.astro presents a step's methods as keyboard-accessible tabs. Each method can contain a command, operation links, and ordered instructions; commands are optional for browser-only steps. The first
method is selected initially; arrow keys, Home and End switch methods. App-detail edits update
both panels without changing the selected tab. Without JavaScript, or when printed, both
methods remain visible. Each panel uses the shared CodeBlock copy control.
Use SectionHeading.astro in MDX for an explicit, short section anchor:
import SectionHeading from '../../../components/SectionHeading.astro';
<SectionHeading id="distribution" title="Does the App Fair require exclusivity?" />The heading defaults to H2 (level={3} or level={4} selects a lower level). Its link icon
appears on hover or keyboard focus, stays visible on touch devices, and works without JavaScript.
Keep the ID stable when changing the question. FAQ entries use this component in
src/content/pages/docs/faq.mdx.