Files
reactbin/specs/005-ui-polish/tasks.md
agatha 9246f75fdd Feat: Polish Angular UI with cohesive design system
Introduces a shared CSS custom property token layer and applies it
across all five views (library, upload, detail, login, app shell).
Each view now has intentional loading, empty, and error states.

- styles.css: 13 design tokens on :root; shimmer skeleton animation
- Library: 150ms-debounced skeleton loading, empty state with /upload
  link, error card with retry, card hover lift, broken-image fallback
- Upload: token-styled drop-zone, Uploading… spinner, 4s success
  banner, distinct validation vs. network error messages
- Detail: image skeleton, network error card (separate from 404
  not-found card), Owner actions panel, danger tag error styling,
  broken-image fallback
- Login: vertically centred surface card, danger field/server errors,
  Signing in… disabled button
- App shell: 48px fixed header, app name left, sign-out right, no
  reflow on auth state change
- All 24 ESLint errors resolved (including pre-existing auth spec
  issues); ng build and ng lint pass clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 20:03:56 +00:00

18 KiB
Raw Permalink Blame History

Tasks: UI Polish & Design System

Input: Design documents from specs/005-ui-polish/ Prerequisites: plan.md ✓, spec.md ✓, research.md ✓, quickstart.md ✓

Tests: Component spec tests are included per §5.1 (TDD non-negotiable). Tests are written first and must fail before implementation begins. Karma/Jasmine via Angular CLI test runner.

Organization: Phase 2 (design token layer) blocks all user story phases. User story phases (Phase 37) are independent of each other and can proceed in parallel after Phase 2 completes.

Milestone mapping (cross-reference with plan.md and quickstart.md): Phase 2 = M1 (Tokens) | Phase 3 = M2 (Library) | Phase 4 = M3 (Upload) | Phase 5 = M4 (Detail) | Phase 6 = M5 (Login) | Phase 7 = M6 (Shell)

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies on incomplete tasks)
  • [Story]: Which user story this task belongs to (US1US5)
  • All component files are under ui/src/app/

Phase 1: Setup

Purpose: Verify baseline state before any changes are made.

  • T001 Confirm ng build passes with zero errors in ui/ (baseline gate before any changes)

Phase 2: Foundational — M1: Design Token Layer

Purpose: Establish the shared CSS custom property layer in ui/src/styles.css. This is the blocking prerequisite for all five user story phases — no component work begins until these tokens exist.

⚠️ CRITICAL: No user story phase can begin until T004 passes.

  • T002 Add 13 CSS custom property tokens to :root in ui/src/styles.css: --bg, --surface, --surface-raised, --border, --border-focus, --text, --text-muted, --accent, --accent-text, --danger, --danger-text, --radius, --radius-chip, --transition — use exact values from research.md Decision 5
  • T003 Add @keyframes shimmer animation and .skeleton utility class to ui/src/styles.css using the gradient pattern from research.md Decision 2
  • T004 Confirm ng build passes with zero errors after token additions (M1 gate — components unchanged, visual output identical)

Checkpoint: Design token layer complete. User story phases 37 may now start.


Phase 3: User Story 1 — Library Feels Complete (Priority: P1) 🎯 MVP

Goal: The library view has skeleton loading, a styled empty state, an error state with retry, polished cards with hover lift, image error fallback, and responsive layout at 375 px.

Independent Test: Throttle network to Slow 3G and hard-refresh / — confirm shimmer skeleton appears. Remove all images — confirm styled empty state with "Upload your first image" link. Stop API — confirm error card with Retry button. Hover a card — confirm 2 px lift. Set viewport to 375 px — confirm no horizontal scrollbar.

Tests for User Story 1 (TDD — write first, confirm failure before T008) ⚠️

  • T005 [US1] Add component tests for showSpinner debounce flag, error flag, skeleton card count, empty-state link, error card retry button, and onImgError handler in ui/src/app/library/library.component.spec.ts

Implementation for User Story 1

  • T006 [P] [US1] Replace all hardcoded colour and spacing values with CSS token variables in the component styles block of ui/src/app/library/library.component.ts
  • T007 [US1] Replace loading = true boolean with showSpinner = false and add 150 ms debounce using timer(150).pipe(takeUntil(req$)) from research.md Decision 3 in ui/src/app/library/library.component.ts
  • T008 [US1] Add skeleton loading grid: while showSpinner is true render 8 <div class="skeleton card-skeleton"> placeholders at the same dimensions as real cards in ui/src/app/library/library.component.ts
  • T009 [US1] Add error = false flag; set it on list() failure; render an error card with plain-language message and "Retry" button that calls load() in ui/src/app/library/library.component.ts
  • T010 [US1] Replace the plain empty-state <p> with a centred panel: Unicode icon (✦), larger muted heading, and a routerLink="/upload" "Upload your first image" link in ui/src/app/library/library.component.ts
  • T011 [US1] Add card hover effect: transform: translateY(-2px) and box-shadow with transition: var(--transition) using --surface-raised in ui/src/app/library/library.component.ts
  • T012 [US1] Add (error)="onImgError($event)" on the card thumbnail <img> and implement onImgError with an inline SVG placeholder (guard against recursive fallback per research.md Decision 4) in ui/src/app/library/library.component.ts
  • T013 [US1] Check the auto-fill minmax() value in the grid at 375 px: if cards overflow horizontally, reduce min card width to 160px and record the change; if no overflow, document "verified at 160px — no change needed" in a code comment in ui/src/app/library/library.component.ts

Checkpoint: Library view is fully functional with all loading/empty/error/responsive states.


Phase 4: User Story 2 — Upload Form Feels Finished (Priority: P1)

Goal: The upload form has a visually distinct drop-zone, visible in-progress state ("Uploading…" + spinner), a success banner with auto-dismiss, distinct validation vs. network error messages, and a clearly disabled button style.

Independent Test: Navigate to /upload — confirm dashed drop-zone border. Select a large file and click Upload — confirm button shows "Uploading…" and is disabled. After success — confirm green banner appears then disappears after 4 s. Attempt to upload a .txt file — confirm "Unsupported file type" inline error.

Tests for User Story 2 (TDD — write first, confirm failure before T016) ⚠️

  • T014 [US2] Add component tests for loading button-disabled state, "Uploading…" label, showSuccess banner visibility, auto-dismiss timer, validation error message, and network error message in ui/src/app/upload/upload.component.spec.ts

Implementation for User Story 2

  • T015 [P] [US2] Replace all hardcoded colour values with CSS token variables in the component styles block of ui/src/app/upload/upload.component.ts
  • T016 [US2] Style the drop-zone with a dashed --accent-coloured border at 40% opacity; add an active drag state that brightens the border to full --accent in ui/src/app/upload/upload.component.ts
  • T017 [US2] Change submit button label to "Uploading…" and add a CSS spinner <span> inside the button while loading = true in ui/src/app/upload/upload.component.ts
  • T018 [US2] Add showSuccess = false and uploadedFilename = ''; after a successful upload set both, show a green-tinted banner with filename, "Upload another" link, and "View in library" routerLink, then auto-dismiss after 4 s using setTimeout in ui/src/app/upload/upload.component.ts
  • T019 [US2] Show distinct inline error messages: validation errors (wrong type/size from API) show the specific problem; network/server errors show a generic retry message — both rendered below the form without a page reload in ui/src/app/upload/upload.component.ts
  • T020 [US2] Apply --text-muted colour and opacity: 0.5 to the disabled button style to make the disabled state visually unmistakable in ui/src/app/upload/upload.component.ts

Checkpoint: Upload form communicates every state clearly and prevents duplicate submission.


Phase 5: User Story 3 — Detail Page Is Well Organised (Priority: P1)

Goal: The detail view has a loading skeleton, a network error state with retry, a styled not-found card with back link, a grouped "Owner actions" panel for write controls, danger-styled tag errors, and a broken-image fallback.

Independent Test: Throttle to Slow 3G and navigate to /images/<id> — confirm skeleton appears. Stop the API and hard-refresh a detail page — confirm error card with retry (not a blank page). Navigate to /images/00000000-0000-0000-0000-000000000000 — confirm not-found card with "Back to library" button. Log in and open a detail page — confirm write controls are grouped. Open detail page while logged out — confirm write controls absent. Add tag with ! character — confirm danger-coloured inline error.

Tests for User Story 3 (TDD — write first, confirm failure before T023) ⚠️

  • T021 [US3] Add component tests for skeleton visibility while loading=true, network error card when fetch fails (non-404), not-found card when !image && !loading && !error, tag error --danger style application, and onImgError handler in ui/src/app/detail/detail.component.spec.ts

Implementation for User Story 3

  • T022 [P] [US3] Replace all hardcoded colour values with CSS token variables in the component styles block of ui/src/app/detail/detail.component.ts
  • T023 [US3] Add skeleton loading layout while loading = true: a full-width grey .skeleton rectangle for the image area and two rows of .skeleton chip placeholders below in ui/src/app/detail/detail.component.ts
  • T024 [US3] Add error = false flag; set it on API fetch failure (non-404 errors); render an error card with plain-language "Failed to load image" message, "Back to library" link, and a "Retry" button that calls the fetch again — distinct from the not-found state in ui/src/app/detail/detail.component.ts
  • T025 [US3] Replace the plain !image && !loading && !error paragraph with a styled not-found card: centred layout, muted Unicode icon, "Image not found" <h2>, and a "Back to library" routerLink="/" button in ui/src/app/detail/detail.component.ts
  • T026 [US3] Wrap the tag-edit input and delete button in a visually distinct "Owner actions" <section> with a --surface panel, --border top separator, and token-based padding/gap in ui/src/app/detail/detail.component.ts
  • T027 [US3] Apply color: var(--danger) and border-left: 3px solid var(--danger) with left-padding to the tagError inline error element in ui/src/app/detail/detail.component.ts
  • T028 [US3] Add (error)="onImgError($event)" on the full-size <img> and implement onImgError with an inline SVG broken-link placeholder (guard against recursive fallback) in ui/src/app/detail/detail.component.ts

Checkpoint: Detail page handles all states (loading, error, not-found, success) gracefully and clearly separates read from write content.


Phase 6: User Story 4 — Login Page Matches the Design (Priority: P2)

Goal: The login page uses the shared dark design system, displays field-level validation errors and server error messages in --danger colour without a page reload, shows a single server error below the form on bad credentials, and disables the button with "Signing in…" label while in-flight.

Independent Test: Navigate to /login — confirm dark background, surface card, same font as library. Click Sign In with empty fields — confirm field-level errors without page reload. Enter wrong credentials — confirm single error message below form in danger colour; fields retain values. Throttle network and submit valid credentials — confirm button shows "Signing in…" and is disabled.

Tests for User Story 4 (TDD — write first, confirm failure before T031) ⚠️

  • T029 [US4] Add component tests for field-level validation error display on empty submit, errorMessage server error paragraph visibility after failed login, "Signing in…" button label while loading=true, and fields-not-cleared behaviour in ui/src/app/login/login.component.spec.ts

Implementation for User Story 4

  • T030 [P] [US4] Replace all hardcoded colour values with CSS token variables in the component styles block of ui/src/app/login/login.component.ts
  • T031 [US4] Centre the login card vertically (height: 100vh; display: flex; align-items: center; justify-content: center) and wrap the form in a --surface card with --radius border-radius and a 1px solid var(--border) border in ui/src/app/login/login.component.ts
  • T032 [US4] Apply color: var(--danger) to field-level reactive-form validation error <span> elements for both username and password fields, and to the errorMessage server error paragraph below the form in ui/src/app/login/login.component.ts
  • T033 [US4] Change submit button label to "Signing in…" and add disabled attribute while loading = true; style the button with --accent background and --accent-text foreground matching other views in ui/src/app/login/login.component.ts

Checkpoint: Login page is visually consistent with the rest of the app and communicates all form states.


Phase 7: User Story 5 — App Shell Is Consistent (Priority: P2)

Goal: Every page shares a 48 px fixed-height header with the app name on the left and the sign-out control on the right (when authenticated). The header uses --surface background and --border bottom border and does not reflow page content on auth state change.

Independent Test: Log in and navigate between library, upload, and detail — confirm identical 48 px header on all pages. Log out — confirm sign-out control disappears but header height is unchanged. Visit library without logging in — confirm header is present but sign-out control absent.

Tests for User Story 5 (TDD — write first, confirm failure before T036) ⚠️

  • T034 [US5] Add component tests for header 48 px height, sign-out button visibility when authenticated, sign-out button absence when unauthenticated, sign-out action redirecting to /login, and header height unchanged between auth states in ui/src/app/app.component.spec.ts

Implementation for User Story 5

  • T035 [P] [US5] Replace all hardcoded colour values with CSS token variables in the component styles block of ui/src/app/app.component.ts
  • T036 [US5] Style the header <header> element with height: 48px, background: var(--surface), border-bottom: 1px solid var(--border), and a flex layout placing the app name text on the left and the sign-out button on the right; use color: var(--text-muted) and a token-based hover state for the sign-out button; ensure height is declared on the host element so the 48 px is preserved regardless of sign-out button visibility in ui/src/app/app.component.ts

Checkpoint: All five views share a coherent shell. Application feels like one product.


Phase 8: Polish & Cross-Cutting Concerns

Purpose: Linting gate, build validation, responsive checks, and visual acceptance walk-through across all milestones.

  • T037 [P] Run ng lint in ui/ and confirm zero ESLint and Prettier violations across all modified component files (§7.3 gate)
  • T038 [P] Run ng build in ui/ and confirm zero TypeScript or template errors across all modified components
  • T039 [P] Run ng test --watch=false in ui/ (or equivalent build-time check) and confirm all new component spec tests pass with zero regressions
  • T040 [P] Verify upload form layout at 375 px viewport (Chrome DevTools device toolbar → iPhone SE): confirm no horizontal scrollbar and all form elements are usable in ui/src/app/upload/upload.component.ts
  • T041 [P] Verify detail page layout at 375 px viewport: confirm image, tags, and (when authenticated) owner actions are all visible without horizontal overflow in ui/src/app/detail/detail.component.ts
  • T042 Walk through specs/005-ui-polish/quickstart.md scenarios M1M6 in a running docker compose up stack and confirm every visual acceptance criterion passes

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — start immediately
  • Foundational (Phase 2): Depends on Phase 1 — BLOCKS all user story phases
  • User Stories (Phases 37): ALL depend on Phase 2 (T004 gate); independent of each other
  • Polish (Phase 8): Depends on all desired user story phases completing

User Story Dependencies

  • US1 Library (Phase 3, P1): Can start after Phase 2 — no dependency on US2US5
  • US2 Upload (Phase 4, P1): Can start after Phase 2 — no dependency on US1, US3US5
  • US3 Detail (Phase 5, P1): Can start after Phase 2 — no dependency on US1US2, US4US5
  • US4 Login (Phase 6, P2): Can start after Phase 2 — no dependency on US1US3, US5
  • US5 App Shell (Phase 7, P2): Can start after Phase 2 — best done last as it wraps all views, but technically independent

Within Each User Story Phase

  1. Write component spec tests first (TDD) — they MUST fail before implementation
  2. Token replacement task [P] can run alongside test writing (different files)
  3. Implementation tasks follow in sequence (each new feature depends on the test that exercises it)

Parallel Execution Examples

Running US1 (Library) startup in parallel

# Start simultaneously after Phase 2 completes:
Task T005: Write library component tests (spec file)
Task T006: Apply CSS tokens to library component styles

Running all three P1 stories in parallel (Phase 3, 4, 5)

# All can start simultaneously after T004 (Phase 2 gate):
Phase 3 (US1): T005 → T006/T007 → T008 → T009 → T010 → T011 → T012 → T013
Phase 4 (US2): T014 → T015/T016 → T017 → T018 → T019 → T020
Phase 5 (US3): T021 → T022/T023 → T024 → T025 → T026 → T027 → T028

Implementation Strategy

MVP First (US1 + US2 + US3 — all P1 stories)

  1. Complete Phase 1: Baseline verification
  2. Complete Phase 2: Design token layer (CRITICAL gate)
  3. Complete Phases 3, 4, 5 in parallel or sequence (all P1)
  4. STOP and VALIDATE: Run quickstart.md M1M4 scenarios
  5. Add Phase 6 (US4 Login) and Phase 7 (US5 Shell) for complete polish

Incremental Delivery

  1. Phase 1 + Phase 2 → Token layer live (no visible change)
  2. Phase 3 (US1) → Library feels complete → Demo
  3. Phase 4 (US2) → Upload flow polished → Demo
  4. Phase 5 (US3) → Detail page organised → Demo
  5. Phase 6 (US4) + Phase 7 (US5) → Full design system applied → Final demo
  6. Phase 8 → Lint clean, build clean, all tests pass, quickstart validated

Notes

  • [P] tasks have no file conflicts with other concurrent [P] tasks in the same phase
  • TDD order is mandatory: spec tests must be written and confirmed failing before implementation tasks
  • All five component files are standalone Angular components — changes are isolated
  • ng build is the type-check gate; Karma tests require the full Docker stack for browser runner
  • No new npm dependencies are introduced in any task
  • Commit after each milestone (M1M6) for clean rollback points