Design an interface for web and mobile contexts
Outcome
Section titled “Outcome”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.
What is new and what is reused
Section titled “What is new and what is reused”- 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, anddocs/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, validatedWorkItemsApi, 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.
Required result
Section titled “Required result”You have completed this lesson when:
feature/cross-platformbegins from clean synchronized and verified Stage 3main;docs/cross-platform-design.mdnames 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
/apicontract 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.htmlkeeps a zoomable device-width viewport and addsviewport-fit=cover;- the outer application shell uses safe-area environment values with zero fallbacks and applies each edge once;
- the shell uses a
100vhfallback followed by100dvhso 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.
Establish the accepted Stage 3 baseline
Section titled “Establish the accepted Stage 3 baseline”Open PowerShell in the Delivery Board repository:
git switch maingit pull --ff-onlygit statusnpm cinpm testnpm run typechecknpm run lintnpm run buildgit switch -c feature/cross-platformgit statusStart 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:
git branch --show-currentgit log --oneline --decorate --graph --all -16git statusContinue 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.
Write the cross-platform design contract
Section titled “Write the cross-platform design contract”Create docs/cross-platform-design.md with this 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 recoveryComplete the critical journey through:
- cold board load;
- valid status filter;
- work-item detail navigation;
- browser or system Back;
- status update;
- update feedback and focus;
- API-unavailable recovery; and
- 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:
<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.
Put safe-area ownership on the outer page
Section titled “Put safe-area ownership on the outer page”The existing shell uses page margin for outer space. Replace that outer-space ownership with body padding. Apply each safe edge once:
: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:
- a browser that does not understand
dvhkeeps100vh; - a browser that understands
dvhuses the current dynamic viewport height; and min-heightlets 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:
@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:
: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.
Audit controls for mixed input
Section titled “Audit controls for mixed input”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:
.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.
Measure rendered targets
Section titled “Measure rendered targets”Open each route in the browser. Use the Accessibility pane or this read-only Console expression:
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.
Keep hover as an enhancement
Section titled “Keep hover as an enhancement”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:
@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.
Check portrait and short landscape layout
Section titled “Check portrait and short landscape layout”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.
Preserve source and focus order
Section titled “Preserve source and focus order”CSS can visually rearrange Grid or Flex children. Keep the visual order aligned with DOM order:
- product identity;
- primary navigation;
- route heading and explanation;
- route controls;
- feedback;
- 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.
Verify motion preference
Section titled “Verify motion preference”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 browser cross-platform matrix
Section titled “Run the browser cross-platform matrix”Run the automated gate before manual evidence:
npm testnpm run typechecknpm run lintnpm run buildStart Express and the production client preview in separate terminals. Record the exact URLs, browser version, operating system, branch, and commit.
Route and state matrix
Section titled “Route and state matrix”Verify at desktop, narrow portrait, and short landscape:
- cold
/load; - populated and empty filter results;
- one valid and one unsupported filter URL;
- Status guide navigation;
- one known and one unknown item route;
- unmatched-path recovery;
- one successful status update;
- one filtered-card removal with feedback focus;
- initial API failure and retry; and
- 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.
Input matrix
Section titled “Input matrix”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.
Display matrix
Section titled “Display matrix”Check:
- 320 CSS pixels;
568 × 320short 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.
Prepare the native handoff
Section titled “Prepare the native handoff”Add these Native pending rows to the design document:
- Android project builds from committed source;
- Vite
distis 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.
Update README
Section titled “Update README”Record:
- browser and future Android contexts;
- shared product journey and route structure;
- viewport, safe-area, dynamic-height, target, hover, focus, and motion decisions;
- browser matrix and exact evidence limits;
- current relative API mode;
- planned packaged demonstration mode and its visible label;
- why neither
127.0.0.1nor a Vite proxy is a packaged production API; - no database, public API, offline synchronization, or iOS requirement;
- the native-pending checklist; and
- the next lesson’s required tools and first action.
Run the final gate
Section titled “Run the final gate”Stop every project process. Run:
npm testnpm run typechecknpm run lintnpm run buildgit status --shortgit diff --checkgit diffInspect the staged change:
git add index.html src/styles.scss README.md docs/cross-platform-design.mdgit diff --cached --checkgit diff --cachedgit commit -m "feat: adapt interface for cross-platform contexts"git statusThe 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.
- Confirm that feature/cross-platform begins from synchronized and verified Stage 3 main.
- Point to the critical journey and identify its visible result, control, focus, and state owner at every step.
- Explain why viewport width is not a device, platform, or input guarantee.
- Distinguish the browser runtime, Android WebView, Capacitor container, and Android operating system.
- Identify which product behavior remains shared across the required contexts.
- Identify which system-bar, lifecycle, system Back, WebView, and packaged-network checks remain native pending.
- Confirm that browser emulation is recorded as browser evidence rather than Android proof.
- Explain why the relative Vite-proxied /api request does not become a packaged Express server.
- Confirm that the next lesson has an explicit, visibly labeled demonstration-data decision rather than a hidden localhost assumption.
- Explain why demonstration mode is not API, persistence, deployment, or offline-synchronization evidence.
- Confirm that no VITE_ value contains a secret or credential.
- Inspect the viewport meta element and confirm device width, initial scale, viewport fit, and no zoom restriction.
- Inspect the four safe-area variables and confirm each has a zero fallback.
- Confirm that one outer owner applies each safe-area value once.
- Explain the order and purpose of 100vh followed by 100dvh.
- Confirm that long route content grows and scrolls instead of being clipped by a fixed viewport height.
- Apply temporary nonzero top and bottom safe values, inspect the result, and remove the override.
- Measure primary navigation, filter, status action, detail, recovery, and retry targets in their rendered states.
- Confirm that required targets meet 24 CSS pixels and keep the comfortable 2.75-rem size where designed.
- Confirm that inline text links were not expanded into overlapping blocks.
- Confirm that hover styles are enhancements and every state, name, and action works without hover.
- Use keyboard, fine pointer, and emulated touch and confirm one intentional activation plus visible focus.
- Verify 320 by 568 portrait and 568 by 320 landscape without horizontal scrolling or covered actions.
- Verify 200 percent zoom, long content, complete wrapping, and unclipped focus.
- Verify reduced motion without removing pending, success, or failure feedback.
- Use Back and Forward across routes and filters and confirm URL, view, title, and focus remain synchronized.
- Change viewport orientation while state is current and confirm that layout change does not reset the application.
- Run initial failure and retry plus update failure and retained state at narrow and desktop layouts.
- Run the complete client and API tests, typecheck, lint, and production build.
- Read README and docs/cross-platform-design.md and confirm decisions, browser evidence, limits, and native-pending checks agree.
- 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.
Official references
Section titled “Official references”- Capacitor 8 introduction — web-first native runtime and supported platform model
- Capacitor 8 environment setup — current Node, Android Studio, SDK, and platform requirements
- Capacitor workflow — build, sync, and native-test sequence
- MDN: Viewport metadata — device width, scaling, interactive widgets, and
viewport-fit - MDN: CSS environment variables — safe-area values and
env()fallbacks - MDN: CSS length values — small, large, and dynamic viewport units
- MDN: pointer — primary pointing-device accuracy
- MDN: prefers-reduced-motion — user motion preference
- WCAG 2.2: Target Size (Minimum) — 24 CSS-pixel minimum and exceptions
- WCAG 2.2: Reflow — 320 CSS-pixel vertical-content boundary
- WCAG 2.2: Resize Text — 200 percent text scaling
Optional extensions
Section titled “Optional extensions”The platform contract, safe-area layout, mixed-input audit, browser matrix, documentation, and focused commit complete this lesson.
Next step or safe stopping point
Section titled “Next step or safe stopping point”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.