Files
reactbin/specs/005-ui-polish/spec.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

12 KiB

Feature Specification: UI Polish & Design System

Feature Branch: 005-ui-polish Created: 2026-05-03 Status: Draft Input: User description: "Polish the Angular UI with a cohesive visual design. The three main views — library (image grid), upload form, and image detail — should feel intentional and finished. Add proper loading states, empty states, and error states to each view. The overall aesthetic should be dark-themed and minimal, fitting a personal tool used frequently. The login page should also match the design system."

User Scenarios & Testing (mandatory)

User Story 1 — Library Feels Complete (Priority: P1)

The owner opens the app and is greeted by a polished image grid. While images load, something visually coherent fills the space so the page doesn't feel broken. If the library is empty, a helpful prompt explains what to do. If the request fails, a clear error message appears with a way to retry.

Why this priority: The library is the landing page and the most-visited view. Its quality sets first impressions for every session.

Independent Test: Open the app with no images uploaded — confirm an intentional empty state is shown. Upload an image, return to the library — confirm the grid renders cleanly with consistent card sizing. Throttle the network — confirm a loading indicator appears before images arrive.

Acceptance Scenarios:

  1. Given the library is loading, When the page first renders, Then a skeleton or spinner occupies the grid area so the layout does not jump or appear blank.
  2. Given no images have been uploaded, When the library loads successfully, Then an empty-state message is shown explaining that no images exist yet, with a visible prompt to upload the first image.
  3. Given the image fetch fails (network error), When the library loads, Then an error message is shown with a retry action; the page does not display a blank grid or a raw error code.
  4. Given images exist, When the library renders, Then all image cards have consistent size, spacing, and visual weight; tag chips are readable and do not overflow their cards.

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

The owner navigates to the upload page and finds a form that clearly communicates its state at every step: idle with a helpful drop-zone, active while uploading with visible progress, and resolved with success or a plain-language error.

Why this priority: Upload is the primary write action. A rough upload experience erodes confidence in the whole tool.

Independent Test: Upload a valid image and confirm the flow from drop-zone through in-progress indicator to success result is smooth and clearly communicated. Attempt an upload with an invalid file type and confirm a plain-language validation error appears without a page reload.

Acceptance Scenarios:

  1. Given the upload page is idle, When no file is selected, Then a drop-zone with clear instructions is visible; the submit button is visibly disabled.
  2. Given a file is selected and uploading, When the upload is in progress, Then the submit button is disabled and a visible in-progress indicator is shown; the user cannot accidentally submit twice.
  3. Given an upload succeeds, When the server responds, Then a success confirmation is shown and the owner can navigate onward without confusion.
  4. Given an upload fails due to an invalid file type or size, When the server responds, Then a plain-language error message is shown identifying the problem; the form remains usable for another attempt.
  5. Given an upload fails due to a network or server error, When the server responds, Then a generic error message is shown with guidance to retry.

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

The owner opens an image's detail page and finds the image prominently displayed, tag management clearly grouped, and write controls (edit tags, delete) visually distinct from read content. Visitors who are not logged in see the image and tags but no write controls. Loading and error states are handled gracefully.

Why this priority: The detail page is where tag curation and deletion happen — the two most common editing actions after upload.

Independent Test: Open a detail page while logged in — confirm write controls are visible and clearly grouped. Open the same page while logged out — confirm write controls are hidden. Navigate to a non-existent image ID — confirm a not-found state is shown rather than a blank or broken page.

Acceptance Scenarios:

  1. Given the detail page is loading, When the route is first entered, Then a loading indicator is shown in place of the image and metadata.
  2. Given the image exists and the owner is logged in, When the page renders, Then the image is the focal point; tags are displayed below; tag editing and delete controls are clearly grouped and visually differentiated from read content.
  3. Given the image exists and the visitor is not logged in, When the page renders, Then the image and tags are visible; no tag-edit input or delete button is present.
  4. Given a non-existent image ID is requested, When the page loads, Then a not-found state is shown with a link back to the library; no raw error code or blank area is displayed.
  5. Given a tag update fails, When the owner submits a tag change, Then an inline error message explains the failure; the image and other tags remain visible.

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

The owner lands on the login page (directly or after a redirect) and finds a form that visually belongs to the same application as the library and detail page. The form clearly communicates validation errors and submission state.

Why this priority: Login is visited infrequently. A consistent visual treatment matters, but functional correctness (already implemented) is more critical than aesthetic alignment.

Independent Test: Navigate to /login directly — confirm the page uses the same colour scheme, typography, and spacing as the rest of the app. Submit with empty fields — confirm visible validation errors appear without a page reload.

Acceptance Scenarios:

  1. Given the login page loads, When the owner views it, Then the page uses the same dark background, colour palette, and typographic scale as all other views.
  2. Given the owner submits with empty username or password, When the form is submitted, Then inline validation messages appear on the relevant fields without a page reload or server round-trip.
  3. Given the owner submits invalid credentials, When the server rejects them, Then a single error message is shown below the form; the fields are not cleared.
  4. Given the form is submitting, When the request is in-flight, Then the submit button is disabled and shows an in-progress label so the owner cannot submit twice.

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

Every page shares a consistent outer frame: a slim header that shows the sign-out control when logged in. The header does not compete with page content for visual attention but is always present and usable.

Why this priority: A coherent shell ties the individual views together into a single application rather than a collection of pages.

Independent Test: Navigate between library, detail, and upload while logged in — confirm the header is consistent across all views. Sign out and visit a public page — confirm the sign-out control is absent.

Acceptance Scenarios:

  1. Given the owner is logged in, When viewing any page, Then a slim header is present with a sign-out control; it does not draw excessive visual attention away from the page content.
  2. Given the visitor is not logged in, When viewing the library or a detail page, Then the header is present but contains no sign-out control.
  3. Given the owner clicks sign out in the header, When the action completes, Then they are redirected to the login page and the header no longer shows the sign-out control.

Edge Cases

  • What happens if an image fails to load (broken URL or storage outage)? The card or detail view should show a placeholder, not the browser's default broken-image icon.
  • What happens on a very narrow viewport (mobile browser)? Cards should stack or resize; the layout must not overflow horizontally.
  • What happens if a tag is very long? Tag chips must truncate or wrap without breaking the card or detail layout.
  • What happens during slow network conditions? Loading states must appear promptly and not flash on fast connections.

Requirements (mandatory)

Functional Requirements

  • FR-001: Every view (library, upload, detail, login) MUST display a loading indicator while async data or actions are in progress.
  • FR-002: The library MUST display a meaningful empty-state message with a call to action when no images exist.
  • FR-003: All four views MUST display plain-language error messages when an operation fails; raw HTTP status codes or stack traces MUST NOT be shown to the user.
  • FR-004: The upload form MUST disable the submit control while an upload is in progress to prevent duplicate submissions.
  • FR-005: The detail page MUST show a not-found state (with a back link) when the requested image does not exist.
  • FR-006: Write controls on the detail page (tag editing, delete) MUST be hidden for unauthenticated visitors and visible only to the logged-in owner.
  • FR-007: All views MUST share a consistent set of visual tokens: background colours, text colours, spacing scale, border radii, and interactive-element styles.
  • FR-008: The application MUST be usable on viewports as narrow as 375 px (iPhone SE width) without horizontal overflow.
  • FR-009: Loading indicators MUST NOT flash on connections fast enough to resolve in under 150 ms; debounced or skeleton-based approaches are preferred.
  • FR-010: Broken image assets (failed loads) MUST display a visible placeholder rather than the browser's default broken-image icon.

Key Entities

  • Design token set: The shared palette, spacing scale, and typographic rules that all views derive from (background, surface, border, text-primary, text-muted, accent, danger).
  • Loading state: A visual treatment applied to any view or element while data is being fetched or an action is in progress.
  • Empty state: A purposeful layout shown when a collection has zero items, including explanatory text and a next-action prompt.
  • Error state: A purposeful layout shown when an operation fails, including a plain-language description and (where applicable) a retry action.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: Every view transitions from loading to content (or error/empty) without a layout shift visible to the naked eye.
  • SC-002: All five views pass a visual consistency check: an observer can identify them as belonging to the same application by colour, typography, and spacing alone.
  • SC-003: The library, upload, and detail views each render without horizontal scrollbars on a 375 px wide viewport.
  • SC-004: Each error condition (network failure, validation failure, not-found) produces a user-visible message within the current view — zero conditions result in a silent failure or blank screen.
  • SC-005: Loading indicators do not appear on responses that complete in under 150 ms in a local development environment (no flicker on fast connections).

Assumptions

  • The existing dark colour palette already in the components (#1a1a1a backgrounds, #e0e0e0 text, #4a9eff accent) is the correct base; the polish work refines and extends it rather than replacing it wholesale.
  • No external component library or icon set is introduced; any icons needed are either inline SVG or Unicode characters to avoid new dependencies.
  • The app remains a single-page application; no server-side rendering or route-level transitions are in scope.
  • Mobile layout is "good enough to use" at 375 px rather than a fully optimised mobile-first redesign; a dedicated mobile redesign is out of scope.
  • No new API endpoints are needed; all changes are purely front-end.
  • Animations and transitions are minimal — a single standard duration applied consistently; no complex motion design.
  • FR-006 (hiding write controls for unauthenticated visitors) is already implemented in the detail component; this spec confirms the behaviour is preserved and visually correct, not that it needs to be built from scratch.