high priority low complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

LoginScreen is a StatelessWidget (or minimal StatefulWidget) that receives an Organisation object (or equivalent) via its constructor or from navigation arguments
OrgLabelsProvider (or equivalent context provider) is consumed to display the organisation's logo and name at the top of the screen
The organisation logo renders with a fixed maximum height (e.g., 80dp) and uses a fallback placeholder widget if the logo asset is unavailable
A visible page heading (e.g., 'Sign in to [Org Name]') is rendered as a Text widget with a design-token heading style
The heading is wrapped in Semantics(header: true) so screen readers announce it as a page heading, aiding navigation for blind users
LoginForm is rendered below the branding section, wrapped in KeyboardAwareLayout
The overall layout uses a Column inside a Padding with horizontal screen-edge padding from design tokens
The screen uses Scaffold with no AppBar (the heading inside the content replaces the AppBar) — verify this matches the design
LoginFormBLoC is provided to the widget tree above LoginForm using BlocProvider inside LoginScreen
On BLoC state LoginFormSuccess, LoginScreen listens (via BlocListener) and triggers navigation to the post-login home route

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
data models
Organisation
OrgLabels
performance requirements
Logo image loading must not block the UI — use Image.asset or Image.network with a builder that shows a placeholder immediately
security requirements
Organisation context passed via navigation must be validated — if null or missing, redirect to organisation selection screen rather than crashing
ui components
Scaffold
Column
Semantics (header: true for heading)
Organisation logo widget (Image + fallback)
Text (org name, heading style)
KeyboardAwareLayout
LoginForm
BlocProvider<LoginFormBLoC>
BlocListener<LoginFormBLoC, LoginFormState>

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

In LoginScreen.build(), use BlocProvider(create: (_) => LoginFormBLoC(), child: BlocListener(listener: (context, state) { if (state is LoginFormSuccess) { context.go('/home'); } }, child: _LoginScreenBody(...))). Extract the inner body to a private _LoginScreenBody widget to keep the tree readable. Use OrgLabelsProvider.of(context) (or Riverpod equivalent) to get org labels. For the logo, use a ClipRRect with borderRadius matching design tokens, and provide an errorBuilder that shows a placeholder icon using the org's primary colour from design tokens.

Ensure the Column's mainAxisAlignment keeps content centred vertically on large screens using Spacer widgets or mainAxisAlignment: MainAxisAlignment.center. This screen is the entry point after org selection — treat the Organisation argument as required and add an assert in debug mode.

Testing Requirements

Write widget tests: (1) screen renders org logo and name from provided Organisation object, (2) page heading has Semantics header:true, (3) LoginForm is present in widget tree, (4) when BLoC emits LoginFormSuccess, navigation callback is triggered (mock GoRouter or Navigator), (5) if Organisation argument is null, screen navigates to org selection (test the guard). Integration test: launch LoginScreen with a real or mock Organisation, verify full layout renders without overflow on a 320dp-wide device (small phone).

Component
Login Screen
ui low
Epic Risks (3)
high impact medium prob scope

Automated accessibility checks (e.g., flutter_accessibility_service) may pass while manual VoiceOver/TalkBack testing reveals focus-order issues, missing semantic roles, or live region announcements that fire too early or not at all. Discovering these late risks delaying the MVP release.

Mitigation & Contingency

Mitigation: Conduct manual VoiceOver and TalkBack testing on physical devices at the end of every sprint, not only at release. Define accessibility acceptance criteria per component and include them in the DoD. Use Semantics widgets explicitly rather than relying on implicit semantics from Flutter's default widgets for all interactive elements.

Contingency: Maintain a prioritized accessibility bug backlog separate from the main backlog. If critical VoiceOver issues are found close to release, create an explicit accessibility hotfix sprint before TestFlight distribution to Blindeforbundet testers.

medium impact medium prob technical

Keyboard height varies significantly between iOS and Android, between device sizes (iPhone SE vs iPad), and with third-party keyboards. The KeyboardAwareLayout may not correctly adjust scroll offset in all combinations, causing input fields to remain hidden behind the keyboard on certain device/keyboard configurations.

Mitigation & Contingency

Mitigation: Test on a matrix of devices including iPhone SE (small viewport), a mid-size Android phone, and a tablet. Implement the layout using MediaQuery.viewInsets.bottom rather than a fixed padding value to correctly respond to any keyboard height. Include edge cases for floating keyboards on iPads.

Contingency: If device-specific issues are found after release, implement a bottom-padding fallback using BottomPadding inset and allow users to manually scroll. Log affected device/OS combinations for targeted fixes.

high impact low prob dependency

If the design token system's colour palette is updated without re-running contrast validation, form field labels, error messages, or placeholder text could fall below the WCAG 2.2 AA 4.5:1 ratio, causing a compliance regression.

Mitigation & Contingency

Mitigation: Integrate a contrast ratio validator (e.g., a CI lint step using the contrast-ratio-validator component) that checks all colour pairs used in the login form on every pull request. Document which token pairs are used for labels, errors, and backgrounds in the login form.

Contingency: If a contrast regression is detected post-merge, hot-patch the affected design token value. Do not ship a TestFlight build with known WCAG AA failures to Blindeforbundet testers.