App prototyping
ProtoWiki prototypes either a web experience (Wikipedia in a browser — Vector 2022 or Minerva) or an app experience (the iOS / Android Wikipedia apps). Same repo, same routing, same Codex; different chrome and a different platform axis.
This skill is the app side end to end. The mechanics of adding any prototype
live in protowiki-create-prototype;
component APIs live in
protowiki-components → app-chrome.md.
The 30-second version
<script setup lang="ts">
definePage({
meta: {
// title + description: ask the author, or omit
category: 'prototype',
platform: 'app',
},
})
import AppChromeWrapper from '@/components/app/AppChromeWrapper.vue'
</script>
<template>
<AppChromeWrapper>
<p>App body content.</p>
</AppChromeWrapper>
</template>
Two things make it an app prototype:
platform: 'app'indefinePagemeta — the gallery groups and chips it as App.AppChromeWrapperinstead ofChromeWrapper— phone frame, app top bar, bottom icon nav.
Everything else (file-based routing, gallery copy rules, Codex discipline, deploy) is identical to a web prototype.
Start from a template
Copy the closest src/prototypes/template-app-*/ folder rather than starting
blank — they're the reference implementations for the patterns below.
| Template | What it shows |
|---|---|
template-app-chrome |
Blank-ish shell. Radio pickers for every header variant (explore / search / activity / saved / article) and bottom bar preset — the fastest way to see what the chrome can do |
template-app-article |
Article reading screen: live REST content via <ArticleLive app> — random article, or ?article= / ?lang=; back + search header, platform article toolbar |
template-app-search |
Live multilingual search: AppChromeHeader with a search field, language tabs, recent searches, navigation into the article screen |
template-app-saved |
Saved articles with collection tabs, loading / progress states, and an iOS-vs-Android difference in how filtering is surfaced |
Web vs app at a glance
| Web prototype | App prototype | |
|---|---|---|
| Gallery meta | platform: 'web' (default) |
platform: 'app' |
| Wrapper | ChromeWrapper |
AppChromeWrapper |
| Chrome | Vector 2022 / Minerva header + footer | App top bar + bottom icon nav |
| Platform axis | skin: desktop / mobile (data-skin) |
OS: ios / android (data-app-platform) |
| Article surface | <ArticleLive> |
<ArticleLive app> |
| Width | Full viewport | Phone column via MobileWrapper (inside the wrapper) |
| Theme | theme prop / data-theme |
Same — theming is shared, see protowiki-theme |
App chrome has no skin. AppChromeWrapper never sets data-skin and
doesn't participate in the Vector/Minerva switch — don't reach for
protowiki-skins concepts inside an app prototype, and don't pass skin to
components there. Mobile web (Minerva in a narrow viewport) is a different
thing from the native apps; see protowiki-skins
if that's actually what you want.
iOS vs Android
One preference drives the whole app, kept in sync between App OS in
Appearance settings and ?os=auto|ios|android on the URL. The resolved value
is written to <html data-app-platform="ios|android">:
- Stored preference + URL —
config.appPlatformand?os=always match; changing either updates the other (settings persist inlocalStorage). - Device detection when the preference is
auto— user-agent sniffing, falling back to Android on desktop browsers.
Read it, never write it:
import { useAppPlatform, useIsAndroid, useIsIos } from '@/composables/useAppPlatform'
const platform = useAppPlatform() // Ref<'ios' | 'android'>, read-only
const isIos = useIsIos()
const isAndroid = useIsAndroid()
- Structural differences (different nav items, a different control, a
different order) — branch in script with
useIsIos(). - Visual-only differences — prefer CSS on
[data-app-platform="ios"]/[data-app-platform="android"]over template branching.
The bottom nav presets in src/components/app/appBottomNavItems.ts are the
canonical example — pick the array, don't rebuild the bar:
const bottomNavItems = computed(() =>
isIos.value ? IOS_ARTICLE_BOTTOM_NAV_ITEMS : ANDROID_ARTICLE_BOTTOM_NAV_ITEMS,
)
Presets: {ANDROID,IOS}_MAIN_BOTTOM_NAV_ITEMS for main screens,
{ANDROID,IOS}_ARTICLE_BOTTOM_NAV_ITEMS for the article toolbar. Full item
list and metadata in
app-chrome.md.
Articles in an app
Use the same article surface as the web, with app:
<AppChromeWrapper :left="headerLeft" :right="headerRight" :bottom-nav-items="bottomNavItems">
<ArticleLive app :article="article" :lang="lang" />
</AppChromeWrapper>
app gives you the apps' lead block (lead image, title, short description,
closing rule), collapsed References / External links end matter, static
body headings, Quick facts / More information table widgets, hidden
navboxes — and it pins the skin to mobile, so you never pass skin.
Omit article and you get a random article on each load — source, langs and
vitalLevel tune the draw exactly as they do on the web, and the lead block
fills in once the title resolves. template-app-article/ reads ?article= when
it is there and goes random otherwise.
ArticleSnapshot and ArticleCustom take the same app prop (plus
description / leadImageUrl, which ArticleLive fetches for itself) when a
committed fixture or hand-authored markup suits the prototype better.
Article content stays in one document — REST page/html, never
page/mobile-html / PCS — so <Teleport> and Codex components can reach into
the article via the parserReady event. The reasoning, prop tables, and
behaviour matrix live in
article.md.
Common shapes
| Goal | Composition |
|---|---|
| App shell with header + bottom nav | <AppChromeWrapper>…</AppChromeWrapper> — starter: template-app-chrome/ |
| In-app article reader | <AppChromeWrapper><ArticleLive app article="…"/></AppChromeWrapper> (omit article for a random one) — starter: template-app-article/ |
| Screen with header but no bottom bar | <AppChromeWrapper :show-bottom-menu="false"> (keyboard-heavy screens like search) |
| Fully custom app shell | MobileWrapper + AppChromeHeader / AppBottomMenu directly — see template-app-search/ |
| Screen title instead of a logo | :left="[{ type: 'title', text: 'Saved' }]" |
Gotchas
- Don't pad the body yourself.
AppChromeWrapperalready applies the 24px screen inset to header, main, and bottom nav. An article withappdeliberately drops its own inline padding and bleeds its lead image back out through that gutter. middleneeds both flanks.AppChromeHeaderignoresmiddleunlessleftandrightare both present (with a dev warning). Flanks cap at 4 items.- Bottom nav icons are actions, not tabs. Nothing renders as selected; handle
@navigateyourself. - App OS is not a theme and not a skin. It's its own axis; light/dark still
comes from
theme/data-theme.
Sharing an app prototype
Normal ProtoWiki deploy. ?os= stays pinned on the URL and matches App OS
in Appearance settings — change either one and the other follows. With
?os=auto, device detection runs on each visit; ?os=ios / ?os=android
pin explicitly.
https://<user>.github.io/protowiki/template-app-article?os=auto
https://<user>.github.io/protowiki/template-app-article?os=ios
https://<user>.github.io/protowiki/template-app-article?os=android
See protowiki-deploy.