119 lines
5.4 KiB
Markdown
119 lines
5.4 KiB
Markdown
# The web shell (`apps/web/src/app.html`)
|
|
|
|
`app.html` is the SvelteKit HTML template. Its `<head>` is emitted
|
|
**verbatim on every page**, so it is deliberately kept lean — no
|
|
multi-line explanatory comments, because those would ship to every
|
|
visitor on every route and inflate the document. This file is where
|
|
that rationale lives instead. (Svelte component comments, by contrast,
|
|
are stripped at build time — `preserveComments` is false by default —
|
|
so `.svelte` files may comment freely.)
|
|
|
|
If you change `app.html`, update this doc to match.
|
|
|
|
## Dark mode
|
|
|
|
The site is dark-mode-only by design. `<html class="dark">` makes
|
|
Tailwind's `dark:*` variants fire unconditionally; `<meta
|
|
name="color-scheme" content="dark">` tells the user agent not to
|
|
auto-adapt form controls or scrollbars to a light theme. The light
|
|
Tailwind variants remain in the CSS as an inert fallback should the
|
|
site ever offer a user-toggleable light theme.
|
|
|
|
## Title / description / SEO — per route, not in the shell
|
|
|
|
`app.html` intentionally sets **no** default `<title>` or `<meta
|
|
name="description">`. Title, description, canonical, hreflang,
|
|
OpenGraph, Twitter Card, and JSON-LD are emitted per route via the
|
|
`<Head />` component in each page, rendered into `%sveltekit.head%`.
|
|
SvelteKit always renders a route before committing the HTML, so a page
|
|
is never served without its localized SEO copy. The hreflang set (one
|
|
`<link rel="alternate">` per supported locale) and the JSON-LD block
|
|
are intentional multilingual-SEO surface, not bloat — do not strip
|
|
them to "save weight."
|
|
|
|
## Icons (three assets, distinct purposes)
|
|
|
|
- `favicon.svg` — transparent background, mark only. Browser
|
|
tab/bookmark icon. Reads on both light and dark tab bars because the
|
|
gradient mark has its own color identity.
|
|
- `app-icon.svg` — dark (ink-950) background, mark centered. Default
|
|
PWA icon for OS app launchers, and the `apple-touch-icon` (an iOS
|
|
home-screen shortcut needs a non-transparent square).
|
|
- `app-icon-maskable.svg` — same dark canvas as `app-icon`, but the
|
|
mark fits inside the inner 40% safe radius so Android adaptive-icon
|
|
masks (circle, squircle, teardrop) never crop it.
|
|
|
|
The wordmark is intentionally **not** used for any of these — it is for
|
|
the in-app top-left header and footer only.
|
|
|
|
## iOS / PWA install hints
|
|
|
|
iOS Safari needs these to treat the site as a real PWA on Share → Add
|
|
to Home Screen:
|
|
|
|
- `apple-mobile-web-app-capable: yes` — launch standalone (no browser
|
|
chrome). Without it, the home-screen icon opens a regular Safari tab.
|
|
- `apple-mobile-web-app-status-bar-style: black-translucent` — matches
|
|
the dark theme; the status bar overlays the app's top edge instead of
|
|
stealing ~44pt of layout.
|
|
- `apple-mobile-web-app-title: Morphit` — shorter label under the
|
|
home-screen icon.
|
|
- `mobile-web-app-capable: yes` — the standardized successor of the
|
|
apple-prefixed capability; emitted alongside it for forward
|
|
compatibility (Android Chrome reads the standard one, iOS Safari
|
|
still primarily reads the apple-prefixed).
|
|
|
|
iOS 16.4+ fully supports the PWA install flow including service workers
|
|
and Web Share Target. Older iOS versions still install the icon and
|
|
load the page; they just lack the full PWA feature set.
|
|
|
|
## Font preload
|
|
|
|
Two `<link rel="preload" as="font">` entries for the self-hosted Nunito
|
|
subset (latin 400 + 700, woff2, `crossorigin="anonymous"`) so first
|
|
paint doesn't wait on a font round-trip.
|
|
|
|
## Preflight locale hint (the inline `<script>`)
|
|
|
|
A tiny synchronous script runs before body paint. It reads the
|
|
`?lang=<code>` query parameter (if present), validates it against the
|
|
supported-locale whitelist, and sets
|
|
`document.documentElement.lang` / `dir` so:
|
|
|
|
- screen readers announce the correct language,
|
|
- crawlers that don't run JS see the right `lang` attribute when
|
|
indexing with `Accept-Language` headers,
|
|
- the later `svelte-i18n` `init()` in `hooks.client.ts` picks up the
|
|
same value and serves the correct translations.
|
|
|
|
It is best-effort: with no `lang` param it leaves the default `<html
|
|
lang="en">` intact and lets i18n resolve from `navigator.languages`.
|
|
The whitelist array in the script **must stay in sync with
|
|
`SUPPORTED_LOCALES`**.
|
|
|
|
## No-JavaScript notice (`<noscript>`)
|
|
|
|
A styled banner explaining that browsing (orders, profiles, feedback)
|
|
works without JavaScript by design, but trading (creating orders,
|
|
chatting, signing feedback) needs JS because all cryptography happens
|
|
in the browser — Morphit never sees user keys, and running JS on the
|
|
server would mean holding them. This is intentional UX, kept inline so
|
|
it renders with zero JS.
|
|
|
|
## Service-worker registration
|
|
|
|
Registration is handled by SvelteKit's built-in auto-register
|
|
(`svelte.config.js` → `kit.serviceWorker.register: true`), which
|
|
injects a small inline script at build time that registers
|
|
`/service-worker.js` (compiled from `apps/web/src/service-worker.ts`)
|
|
on `load`.
|
|
|
|
Do **not** manually register a different SW URL in `app.html`: it would
|
|
race with SvelteKit's and silently replace it (same scope `/`, last
|
|
register wins). That was the Part 122 / cp81 bug — a separate `/sw.js`
|
|
was registered manually and superseded the SvelteKit SW, breaking push
|
|
notifications (fixed in cp81-D22). `UpdateBanner.svelte` picks up the
|
|
registration via `navigator.serviceWorker.getRegistration()`
|
|
asynchronously and watches for `updatefound`. See
|
|
[`SERVICE-WORKER-CACHING-DESIGN.md`](./SERVICE-WORKER-CACHING-DESIGN.md)
|
|
for the full design.
|