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>
This commit is contained in:
180
specs/005-ui-polish/spec.md
Normal file
180
specs/005-ui-polish/spec.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user