Skip to content

Design an interface for web and mobile contexts

You will adapt Delivery Board for two required interface contexts before adding a native container:

  • a resizable browser used with keyboard, mouse, touch, browser navigation, and the local Express API; and
  • a future Android WebView used with touch, system bars, portrait and landscape space, application lifecycle changes, and no assumed public API.

The product model and route structure will remain the same. You will document the platform consequences, add safe-area and dynamic-viewport layout, verify comfortable controls and non-hover operation, and record a real browser matrix. The next lesson will package this prepared web build with Capacitor.

What you will practice

  • Distinguish platform, runtime, viewport, input method, device, and native container instead of using mobile as one vague category.
  • Identify which product behavior can stay shared and which environment differences require an explicit decision.
  • Compare a normal browser tab with a Capacitor Android WebView without claiming that one is a complete test of the other.
  • Design a usable API-unavailable result for a packaged client that has no public server.
  • Use viewport-fit, safe-area environment variables, and dynamic viewport units without disabling zoom.
  • Keep navigation and actions operable through keyboard, fine pointer, coarse pointer, and touch-sized targets.
  • Apply hover styles only as enhancements and preserve every action without hover.
  • Verify portrait, landscape, 320 CSS pixels, 200 percent zoom, reduced motion, long content, focus, and browser history.
  • Record evidence, limits, and unresolved native checks for the contributor who will package the application.
  • New: Platform context matrix, WebView boundary, system-bar and safe-area risks, dynamic viewport units, viewport-fit=cover, coarse and fine pointer differences, touch-target audit, portrait and landscape matrices, packaged API-availability decision, and docs/cross-platform-design.md.
  • Reused: Accepted Stage 3 main, semantic React components, React Router URLs and focus management, native controls, Sass tokens and mixins, 2.75-rem interactive controls, flexible card layout, reduced-motion handling, loading and failure states, validated WorkItemsApi, complete tests, Vite build, browser developer tools, Git branch workflow, and focused commits.

Starting point

Before you start

  • Project stage 3 is accepted on remote main and every normal working copy is clean.
  • The Express API and React client pass tests, typecheck, lint, build, direct HTTP checks, and the connected browser matrix.
  • The React client already reflows at 320 CSS pixels, supports 200 percent zoom, exposes visible focus, and does not require hover.
  • Primary navigation, filter, status actions, retry, detail links, and route recovery use semantic controls and accessible names.
  • The app has no text-entry form, native plugin, Android project, iOS project, service worker, database, public API, or production deployment.
  • Android Studio and an emulator are not required in this design lesson. Native proof begins in the next lesson.
  • Keep fictional work-item data and current privacy boundaries.
Current state
Delivery Board works as a connected browser application. Its interface has responsive rules, but it does not yet state which platform differences matter, reserve space for display cutouts or system UI, use a dynamic viewport height, define the packaged API boundary, or separate browser-emulation evidence from native evidence.
First action
Synchronize and verify main, create feature/cross-platform, then add a context matrix and one critical-journey table to docs/cross-platform-design.md before changing HTML or Sass.
First checkpoint
The document distinguishes browser and Android WebView conditions, names shared product behavior, records the packaged API decision, and lists which checks can and cannot be completed before Capacitor exists.
Help trigger
Use the nearest recovery note or ask for help if a viewport name is being used as a device guarantee, the app depends on hover, a target is difficult to activate, safe-area padding appears twice, 100vh hides content behind browser UI, landscape removes a required action, zoom is disabled, the browser Back path differs from a product link, a local API URL is assumed to work inside Android, emulation is being reported as native evidence, or an environment difference has no named owner.

You have completed this lesson when:

  • feature/cross-platform begins from clean synchronized and verified Stage 3 main;
  • docs/cross-platform-design.md names the user journey, contexts, shared behavior, platform differences, risks, decisions, evidence, and next lesson handoff;
  • browser and Android WebView are described as different containers for the web client rather than different product models;
  • the document distinguishes responsive-browser evidence, installed WebView evidence, and unavailable evidence;
  • the required browser mode continues to use the relative /api contract and the local Vite proxy;
  • the future packaged Android mode does not assume that 127.0.0.1, the Vite proxy, or a private development computer is a production API;
  • the next lesson is authorized to add a clearly labeled packaged demonstration adapter because public API deployment is outside this level;
  • demonstration data and memory-only changes are documented as product demonstration evidence, not persistence or server evidence;
  • index.html keeps a zoomable device-width viewport and adds viewport-fit=cover;
  • the outer application shell uses safe-area environment values with zero fallbacks and applies each edge once;
  • the shell uses a 100vh fallback followed by 100dvh so retracting browser UI does not hide the required route result;
  • required controls provide at least the WCAG 2.2 minimum target size and retain the existing comfortable 2.75-rem block size where possible;
  • primary navigation, filters, card actions, detail links, recovery links, and retry remain available without hover;
  • hover styles run only as enhancements for a fine primary pointer that supports hover;
  • wrapping, source order, visible text, accessible names, live or alert feedback, and focus behavior remain correct in portrait and landscape space;
  • no rule disables pinch zoom, browser zoom, text scaling, keyboard focus, or native control behavior;
  • client and API tests, typecheck, lint, and production build pass unchanged or with focused expectation updates;
  • the current-browser matrix covers desktop, 320-pixel portrait, short landscape, 200 percent zoom, keyboard, pointer, emulated touch, reduced motion, long content, routes, and Console;
  • README and the design document state which native checks remain for the Capacitor lesson; and
  • a focused cross-platform-design commit exists on a clean branch.

Open PowerShell in the Delivery Board repository:

Create the cross-platform feature branch
git switch main
git pull --ff-only
git status
npm ci
npm test
npm run typecheck
npm run lint
npm run build
git switch -c feature/cross-platform
git status

Start Express and the built client preview in separate terminals. Repeat one cold load, one valid URL filter, one known detail route, and one status update. Stop both processes.

This check protects the accepted Stage 3 boundary. Do not repair a pre-existing API or client failure with a cross-platform CSS change.

If feature/cross-platform already exists, inspect its base and purpose:

Inspect an existing cross-platform branch
git branch --show-current
git log --oneline --decorate --graph --all -16
git status

Continue it only when the branch begins from accepted Stage 3 main and contains no unrelated product work.

Define platform contexts before screen sizes

Section titled “Define platform contexts before screen sizes”

A platform context combines a runtime container, available system capabilities, input conditions, navigation model, lifecycle, and network boundary. Width is one input to layout. Width alone does not identify a phone, a touch user, Android, or Capacitor.

Use this baseline model:

Condition Resizable browser Future Capacitor Android app
Web runtime Browser page Android WebView inside a native application
Build source Vite web build A copy of the Vite web build in the Android project
Primary input Keyboard, mouse, touch, pen, or mixed Touch is common; keyboard and assistive input remain possible
Window Resizable tab with browser UI Native app window with system bars and display cutouts
Navigation Links, address bar, Back, Forward, reload In-app links plus Android system Back behavior
Lifecycle Tab visible, hidden, reloaded, or closed App active, backgrounded, resumed, or terminated
Network boundary Relative /api through development or deployment origin No Vite proxy after assets are copied; packaged origin has no Express route
Current data mode Validated Express API Clearly labeled local demonstration mode in the next lesson
Persistence Express memory until process restart Demonstration memory until app reload or termination
Current proof Current desktop browser and responsive emulation Not available until the Android project runs

Do not write mobile supports touch as if that excludes keyboards. Do not write desktop supports hover as if a laptop cannot have a touchscreen. Design the shared path for semantic controls first. Use input media features only to enhance it.

Separate framework, container, and operating system

Section titled “Separate framework, container, and operating system”

React renders the interface in both required contexts. Vite builds the web assets. Capacitor will copy those assets into a native project and provide a bridge for approved native plugins. Android supplies the operating system, WebView, system bars, lifecycle, and system Back action.

These layers do not automatically solve each other’s responsibilities:

  • React does not make a layout touch-ready.
  • a narrow browser does not prove WebView behavior;
  • Capacitor does not deploy the Express server;
  • Android Studio does not validate JSON responses;
  • TypeScript does not validate platform input or network data; and
  • a successful native build does not prove the browser build still works.

Create docs/cross-platform-design.md with this structure:

docs/cross-platform-design.md — starting structure
# Cross-platform design: Delivery Board
## Tested state
- Branch and commit:
- Node and npm:
- Browser and operating system:
- Android state: Not added in this lesson
## Critical journey
| Step | User outcome | Required control or feedback | State owner |
| --- | --- | --- | --- |
| 1 | Open the board | Loading then collection or recovery | React and selected data adapter |
## Context matrix
| Concern | Browser decision | Android WebView decision | Evidence state |
| --- | --- | --- | --- |
| Data source | Relative /api | Labeled packaged demonstration adapter | Browser pass; native pending |
## Layout and input risks
## Browser verification
## Native checks for the next lesson
## Limits and recovery

Complete the critical journey through:

  1. cold board load;
  2. valid status filter;
  3. work-item detail navigation;
  4. browser or system Back;
  5. status update;
  6. update feedback and focus;
  7. API-unavailable recovery; and
  8. narrow portrait and short landscape layout.

For each step, state:

  • visible result;
  • control name and semantic role;
  • focus result;
  • source of current data;
  • behavior when a request is pending or fails;
  • whether orientation or viewport change can discard state; and
  • browser and native evidence status.

Decide the packaged data boundary honestly

Section titled “Decide the packaged data boundary honestly”

The accepted browser application requests relative /api URLs. Vite can proxy these requests only while Vite serves the application. Capacitor copies built files into the Android project. It does not copy or run Node, Express, the Vite development server, or the proxy.

The required level does not deploy a public HTTPS API. The next lesson will therefore add a packaged demonstration mode behind the existing WorkItemsApi interface. That mode will:

  • use fictional bundled records;
  • keep updates in memory;
  • show a persistent Demonstration data label;
  • reset on app restart or reload;
  • avoid a network permission and private development address;
  • preserve the same component, route, validation, pending, and feedback contracts where applicable; and
  • remain separate from the browser API mode.

This decision is not offline synchronization. It is not server persistence. It does not prove that a mobile client can reach a deployed API. It gives the packaged interface a deterministic, honest product path within the course boundary.

Do not place a secret in VITE_ configuration. Vite exposes such values to client code. The next lesson will use a public build-mode identifier, not a credential.

Checkpoint: The platform contract separates shared behavior from environment facts

What now works
The critical journey, context matrix, browser and WebView differences, data-mode decision, evidence states, and native-pending checks are explicit before interface CSS changes.
Files changed
docs/cross-platform-design.md, README.md
What remains
Adapt the viewport and outer shell for dynamic browser UI, system-safe edges, narrow portrait space, and short landscape space.
Next action
Inspect index.html and the outer app-shell rules, then add viewport-fit and one safe-area-aware shell without changing route or component ownership.
If it does not work
Return to the context table, name the failing environment condition, and change only the layer that owns it: HTML viewport, outer shell, control style, component markup, or future native configuration.

Make the viewport eligible for safe-area layout

Section titled “Make the viewport eligible for safe-area layout”

Open index.html. Keep the existing device-width and initial-scale values. Add viewport-fit=cover:

index.html — zoomable viewport and full display area
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>

Do not add maximum-scale=1, minimum-scale=1, user-scalable=no, or a script that prevents zoom gestures. A developer preference for an app-like fixed scale does not override the user’s need to zoom.

viewport-fit=cover lets the page use the available display area. It does not keep content away from a cutout, rounded corner, browser control, or system bar. CSS must consume the user-agent safe-area values.

The four safe-area-inset-* values are environment variables. They are commonly zero in an unobstructed desktop window. A zero result is valid. It does not prove that the rule works when an inset is nonzero.

The existing shell uses page margin for outer space. Replace that outer-space ownership with body padding. Apply each safe edge once:

src/styles.scss — dynamic page and safe edges
:root {
--app-safe-top: env(safe-area-inset-top, 0px);
--app-safe-right: env(safe-area-inset-right, 0px);
--app-safe-bottom: env(safe-area-inset-bottom, 0px);
--app-safe-left: env(safe-area-inset-left, 0px);
}
body {
min-width: 20rem;
min-height: 100vh;
min-height: 100dvh;
margin: 0;
padding:
calc(#{$space-8} + var(--app-safe-top))
calc(#{$space-8} + var(--app-safe-right))
calc(#{$space-8} + var(--app-safe-bottom))
calc(#{$space-8} + var(--app-safe-left));
}
.app-shell {
width: min(100%, $content-width);
margin: 0 auto;
}

Keep the shell’s existing surface and internal padding. Remove its old outer width calculation and block margin because the body now owns those gaps.

The two height declarations are ordered fallbacks:

  1. a browser that does not understand dvh keeps 100vh;
  2. a browser that understands dvh uses the current dynamic viewport height; and
  3. min-height lets long content grow and scroll rather than clipping it to one screen.

Do not use height: 100dvh on the long document. A fixed height can create a nested scroll region or hide route content.

Update the narrow rule:

src/styles.scss — narrow safe edges
@media (max-width: 30rem) {
body {
padding:
calc(#{$space-4} + var(--app-safe-top))
calc(#{$space-4} + var(--app-safe-right))
calc(#{$space-4} + var(--app-safe-bottom))
calc(#{$space-4} + var(--app-safe-left));
}
.app-shell {
margin: 0 auto;
padding: $space-4;
}
}

The base spacing remains even when every safe-area value is zero. A nonzero environment value adds only the space needed at that edge.

Do not spread safe-area padding through components

Section titled “Do not spread safe-area padding through components”

Apply the environment values to one outer owner. Do not repeat the same top inset on body, .app-shell, .product-header, and .app-nav. Repetition produces a large unexplained gap on the platform where the inset becomes nonzero.

If a future fixed bottom toolbar visually reaches the screen edge, that toolbar can own the bottom inset instead of the body. Move ownership; do not duplicate it.

Simulate nonzero values without claiming native proof

Section titled “Simulate nonzero values without claiming native proof”

Browser safe-area values can remain zero even in responsive mode. For a focused layout check, temporarily replace one custom property in browser developer tools:

Temporary browser-only safe-area simulation
:root {
--app-safe-top: 2rem;
--app-safe-bottom: 1.5rem;
}

Confirm that content remains visible, the gap appears once, and the document still scrolls. Remove the temporary developer-tools override. Do not add simulated values to source or record this as Android evidence.

Checkpoint: The outer layout responds to viewport and safe edges

What now works
The viewport remains zoomable, viewport-fit allows full-area layout, body owns one set of safe-edge padding, 100dvh follows dynamic browser space with a fallback, long routes scroll normally, and a temporary nonzero inset does not hide or duplicate content.
Files changed
index.html, src/styles.scss, docs/cross-platform-design.md
What remains
Audit controls and interaction for touch, keyboard, fine pointer, coarse pointer, hover absence, and short landscape space.
Next action
List every primary navigation, filter, card, recovery, and retry target, then measure its rendered box and verify its non-hover name and state.
If it does not work
Restore the last passing stylesheet, add viewport metadata alone, then add body height, one edge at a time, and finally narrow spacing. Inspect computed padding before changing a nested component.

WCAG 2.2 Target Size (Minimum) requires a target that can contain a 24-by-24 CSS-pixel square unless an exception applies. Delivery Board already uses 2.75rem, normally 44 CSS pixels, for primary navigation, select, and status buttons. Keep that more comfortable result.

Do not make every inline text link 44 pixels tall. Inline links inside sentences have a target-size exception and should preserve readable line flow. The route and detail links in Delivery Board are standalone actions, so they can use the comfortable size.

Add the standalone links to the target rules:

src/styles.scss — comfortable standalone targets
.app-nav-link,
.filter-control select,
.work-item-action,
.work-item-link,
.route-link {
min-block-size: 2.75rem;
}
.work-item-link,
.route-link {
display: inline-flex;
align-items: center;
padding-block: 0.5rem;
}
.app-nav-link,
.filter-control select,
.work-item-action,
.work-item-link,
.route-link {
touch-action: manipulation;
}

touch-action: manipulation permits normal panning and pinch zoom while declaring that the listed element handles direct activation. It does not make a small target larger. The block size and spacing do that.

Keep native button, select, and a elements. Do not replace them with a div plus click handler to make styling easier.

Open each route in the browser. Use the Accessibility pane or this read-only Console expression:

Browser Console — inspect standalone target boxes
Array.from(
document.querySelectorAll(
'.app-nav-link, .filter-control select, .work-item-action, .work-item-link, .route-link',
),
).map((element) => {
const box = element.getBoundingClientRect();
return {
name: element.textContent?.trim(),
width: Math.round(box.width),
height: Math.round(box.height),
};
});

Measure the currently rendered state. Repeat after text wrapping in portrait and after 200 percent zoom. Do not use the expression to change application state.

The earlier stylesheet gives links and buttons a hover treatment. Move those visual changes into a media query that requires hover and a fine primary pointer:

src/styles.scss — hover enhancement
@media (hover: hover) and (pointer: fine) {
.app-name a:hover,
.route-link:hover,
.work-item-link:hover {
text-decoration-thickness: 0.15em;
}
.app-nav-link:hover {
background: #e6f2eb;
}
.work-item-action:hover:not(:disabled) {
background: #154b34;
}
}

Keep focus, active-route text and aria-current, selected filter value, disabled state, visible pending name, and live feedback outside the hover query. A touch user must receive the complete result without a hover preview.

Do not use a pointer media query to hide content or remove keyboard operation. A platform can have more than one input mechanism, and input mechanisms can change while the page remains open.

Portrait width tests horizontal reflow. Landscape also reduces available block space and can expose fixed or sticky elements that cover content.

Use these browser conditions:

Context CSS viewport Primary risk
Narrow portrait 320 × 568 Text, controls, and cards require horizontal reflow
Short landscape 568 × 320 Header or navigation can consume most visible block space
Tablet portrait 768 × 1024 Lines can become too long or cards can use space poorly
Desktop 1440 × 900 Content measure, mouse and keyboard operation
Zoomed desktop 200% at normal desktop width Effective narrow layout, text enlargement, focus visibility

At each size, verify:

  • complete product name and route heading;
  • wrapping primary navigation with no hidden destination;
  • current route text and aria-current;
  • labeled filter and every option;
  • complete work-item ID, title, description, status, labels, actions, and detail route;
  • loading, initial failure, retry, empty, pending, update failure, and successful feedback when available;
  • route recovery text and link;
  • no fixed header, toolbar, or message covering the focused control;
  • no horizontal page scrolling; and
  • normal vertical document scrolling from first heading to final action.

Do not add a hamburger menu only because the screen is narrow. The two-link navigation already wraps, stays visible, and uses less interaction. A disclosure adds state, focus, name, keyboard, outside-click, and route-close responsibilities that the product does not need.

CSS can visually rearrange Grid or Flex children. Keep the visual order aligned with DOM order:

  1. product identity;
  2. primary navigation;
  3. route heading and explanation;
  4. route controls;
  5. feedback;
  6. collection or recovery.

Do not use positive tabindex values or CSS order to create a different interaction sequence. Use the source order that makes sense in every layout.

The status button already removes its transition under prefers-reduced-motion: reduce. Emulate reduced motion and verify that the action state, focus, pending name, and feedback remain visible without the transform or transition.

Reduced motion is not a reason to remove feedback. It changes presentation, not the user-visible result.

Run the automated gate before manual evidence:

Verify source before browser checks
npm test
npm run typecheck
npm run lint
npm run build

Start Express and the production client preview in separate terminals. Record the exact URLs, browser version, operating system, branch, and commit.

Verify at desktop, narrow portrait, and short landscape:

  1. cold / load;
  2. populated and empty filter results;
  3. one valid and one unsupported filter URL;
  4. Status guide navigation;
  5. one known and one unknown item route;
  6. unmatched-path recovery;
  7. one successful status update;
  8. one filtered-card removal with feedback focus;
  9. initial API failure and retry; and
  10. failed update with retained state and focused feedback.

Use browser Back and Forward across a path change and three filter changes. Rotate or switch viewport dimensions while one item is updated. Current React state must remain current because layout change is not a route reload.

Use:

  • Tab and Shift+Tab through the full current route;
  • Enter on links and buttons;
  • Space on buttons;
  • native select keys;
  • mouse or trackpad activation;
  • emulated touch activation; and
  • a no-hover or coarse-pointer emulation when available.

Confirm visible focus, complete accessible names, one activation per input, no target overlap, and no action that exists only on hover.

Check:

  • 320 CSS pixels;
  • 568 × 320 short landscape;
  • 200 percent zoom;
  • light and dark operating-system color preference if the project supports both;
  • reduced motion;
  • the longest record and error text;
  • temporary nonzero top and bottom safe-area variables;
  • text selection and pinch or browser zoom remain available;
  • no clipped focused outline; and
  • no unexpected Console error or warning.

Record Pass, Issue, Blocked, or Native pending. State the exact condition behind a non-pass result. Do not convert responsive emulation into Android proof.

Checkpoint: The shared interface works across browser layout and input contexts

What now works
Every critical route and state works at desktop, 320-pixel portrait, short landscape, and 200 percent zoom; keyboard, fine pointer, and emulated touch paths remain complete; controls are comfortably sized; hover is optional; reduced motion works; and the evidence states stay honest.
Files changed
index.html, src/styles.scss, docs/cross-platform-design.md, Browser evidence
What remains
Reconcile documentation, record native-pending checks, run the final gate, and create the focused design commit.
Next action
Read the context matrix beside the browser results and list each remaining Android WebView check for the Capacitor lesson.
If it does not work
Return to one route, one viewport, and one input. Record expected and actual layout, focused element, dimensions, and first Console message. Repair the earliest failing ownership boundary.

Add these Native pending rows to the design document:

  • Android project builds from committed source;
  • Vite dist is copied from the current build;
  • app launches without a development server;
  • system bars and nonzero safe areas do not cover content;
  • portrait and landscape both keep required actions;
  • Android system Back follows the documented route policy;
  • app background and resume keep or deliberately reload current state;
  • packaged demonstration mode is visibly named and resets as documented;
  • the application does not request the private development API or 127.0.0.1;
  • WebView Console contains no unexpected error;
  • TalkBack or the available Android accessibility scanner can identify primary controls; and
  • browser tests and build still pass after the native project is added.

Do not install Capacitor or create android/ in this lesson. The clean design commit gives native scaffolding a smaller and reviewable starting point.

Record:

  1. browser and future Android contexts;
  2. shared product journey and route structure;
  3. viewport, safe-area, dynamic-height, target, hover, focus, and motion decisions;
  4. browser matrix and exact evidence limits;
  5. current relative API mode;
  6. planned packaged demonstration mode and its visible label;
  7. why neither 127.0.0.1 nor a Vite proxy is a packaged production API;
  8. no database, public API, offline synchronization, or iOS requirement;
  9. the native-pending checklist; and
  10. the next lesson’s required tools and first action.

Stop every project process. Run:

Verify the cross-platform design commit
npm test
npm run typecheck
npm run lint
npm run build
git status --short
git diff --check
git diff

Inspect the staged change:

Commit the cross-platform design preparation
git add index.html src/styles.scss README.md docs/cross-platform-design.md
git diff --cached --check
git diff --cached
git commit -m "feat: adapt interface for cross-platform contexts"
git status

The final status must be clean on feature/cross-platform. Do not push or add Capacitor yet.

Self-check

Complete these checks against the required result.

  1. Confirm that feature/cross-platform begins from synchronized and verified Stage 3 main.
  2. Point to the critical journey and identify its visible result, control, focus, and state owner at every step.
  3. Explain why viewport width is not a device, platform, or input guarantee.
  4. Distinguish the browser runtime, Android WebView, Capacitor container, and Android operating system.
  5. Identify which product behavior remains shared across the required contexts.
  6. Identify which system-bar, lifecycle, system Back, WebView, and packaged-network checks remain native pending.
  7. Confirm that browser emulation is recorded as browser evidence rather than Android proof.
  8. Explain why the relative Vite-proxied /api request does not become a packaged Express server.
  9. Confirm that the next lesson has an explicit, visibly labeled demonstration-data decision rather than a hidden localhost assumption.
  10. Explain why demonstration mode is not API, persistence, deployment, or offline-synchronization evidence.
  11. Confirm that no VITE_ value contains a secret or credential.
  12. Inspect the viewport meta element and confirm device width, initial scale, viewport fit, and no zoom restriction.
  13. Inspect the four safe-area variables and confirm each has a zero fallback.
  14. Confirm that one outer owner applies each safe-area value once.
  15. Explain the order and purpose of 100vh followed by 100dvh.
  16. Confirm that long route content grows and scrolls instead of being clipped by a fixed viewport height.
  17. Apply temporary nonzero top and bottom safe values, inspect the result, and remove the override.
  18. Measure primary navigation, filter, status action, detail, recovery, and retry targets in their rendered states.
  19. Confirm that required targets meet 24 CSS pixels and keep the comfortable 2.75-rem size where designed.
  20. Confirm that inline text links were not expanded into overlapping blocks.
  21. Confirm that hover styles are enhancements and every state, name, and action works without hover.
  22. Use keyboard, fine pointer, and emulated touch and confirm one intentional activation plus visible focus.
  23. Verify 320 by 568 portrait and 568 by 320 landscape without horizontal scrolling or covered actions.
  24. Verify 200 percent zoom, long content, complete wrapping, and unclipped focus.
  25. Verify reduced motion without removing pending, success, or failure feedback.
  26. Use Back and Forward across routes and filters and confirm URL, view, title, and focus remain synchronized.
  27. Change viewport orientation while state is current and confirm that layout change does not reset the application.
  28. Run initial failure and retry plus update failure and retained state at narrow and desktop layouts.
  29. Run the complete client and API tests, typecheck, lint, and production build.
  30. Read README and docs/cross-platform-design.md and confirm decisions, browser evidence, limits, and native-pending checks agree.
  31. Inspect the staged diff, create the focused design commit, and confirm a clean feature branch.

Explanation: cross-platform means one product with explicit environment contracts

Section titled “Explanation: cross-platform means one product with explicit environment contracts”

Cross-platform design does not mean that every environment is identical. It means that the product keeps a coherent outcome while the implementation responds deliberately to the container, input, viewport, lifecycle, and service boundaries that differ.

This lesson starts with semantic web controls because they already support keyboard, pointer, touch, focus, names, and assistive technology. CSS then adapts available space and pointer presentation. Capacitor will add a native container in the next lesson. Native code is not the first tool for a layout or HTML problem.

The data decision also preserves honesty. The browser stage proved a real local API. A packaged app cannot reach that private workflow without another network architecture. A labeled demonstration adapter keeps the installed interface useful while making the missing public-service boundary visible. A future production project can replace that adapter with an HTTPS API after it designs deployment, authentication, authorization, CORS, storage, and conflict handling.

The platform contract, safe-area layout, mixed-input audit, browser matrix, documentation, and focused commit complete this lesson.

The required result is safely paused when the browser matrix is recorded, temporary inset and failure conditions are restored, every project process is stopped, tests, typecheck, lint, and build pass, the design and README agree, the focused commit exists, and git status is clean on feature/cross-platform.

Continue to Package and test an application with Capacitor. That lesson will add current Capacitor packages, create the Android project, implement the labeled demonstration adapter, build and sync web assets, run the native target when the required Android tools are available, and keep native evidence distinct from browser evidence.