1 Commits

Author SHA1 Message Date
28df9a1261 Feat: Header title links to grid; sign-out redirects to grid
Make the app title a clickable link to / so users can return to the
image grid from any sub-page without the browser back button. Change
the sign-out destination from /login to / since the grid is publicly
accessible and avoids unnecessary friction post-logout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 20:14:35 +00:00
7 changed files with 160 additions and 8 deletions

View File

@@ -1 +1 @@
{"feature_directory": "specs/005-ui-polish"}
{"feature_directory":"specs/006-header-nav-signout"}

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Header Navigation & Sign-Out Destination
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-05-03
**Feature**: [spec.md](../spec.md)
## Content Quality
- [X] No implementation details (languages, frameworks, APIs)
- [X] Focused on user value and business needs
- [X] Written for non-technical stakeholders
- [X] All mandatory sections completed
## Requirement Completeness
- [X] No [NEEDS CLARIFICATION] markers remain
- [X] Requirements are testable and unambiguous
- [X] Success criteria are measurable
- [X] Success criteria are technology-agnostic (no implementation details)
- [X] All acceptance scenarios are defined
- [X] Edge cases are identified
- [X] Scope is clearly bounded
- [X] Dependencies and assumptions identified
## Feature Readiness
- [X] All functional requirements have clear acceptance criteria
- [X] User scenarios cover primary flows
- [X] Feature meets measurable outcomes defined in Success Criteria
- [X] No implementation details leak into specification
## Notes
- All items pass. Both changes are small, independent, and clearly bounded. Spec is ready for `/speckit-plan`.

View File

@@ -0,0 +1,25 @@
# Implementation Plan: Header Navigation & Sign-Out Destination
**Branch**: `006-header-nav-signout` | **Date**: 2026-05-03 | **Spec**: [spec.md](spec.md)
## Summary
Two targeted changes to `ui/src/app/app.component.ts`:
1. Wrap the header app-name text in a router link to `/`.
2. Change the post-sign-out navigation target from `/login` to `/`.
No API changes. No new dependencies. One component file affected.
## Technical Context
**Language/Version**: TypeScript 5 / Angular 19 (standalone components)
**Affected files**: `ui/src/app/app.component.ts`, `ui/src/app/app.component.spec.ts`
**Testing**: Karma / Jasmine
## Constitution Check
| Principle | Status |
|-----------|--------|
| §2.1 Strict separation of concerns | ✅ UI-only change |
| §5.1 TDD non-negotiable | ✅ Tests written first |
| §7.3 Linting non-optional | ✅ ng lint gate in tasks |

View File

@@ -0,0 +1,69 @@
# Feature Specification: Header Navigation & Sign-Out Destination
**Feature Branch**: `006-header-nav-signout`
**Created**: 2026-05-03
**Status**: Draft
**Input**: User description: "Simple updates to the UI. Site title in the header should link to the base URL so that it's quick to get back to the main grid view from any sub-page now or in the future. When a user signs out, they should be sent back to the grid view instead of the sign in form."
## User Scenarios & Testing *(mandatory)*
### User Story 1 — Header Title Links to Grid (Priority: P1)
The owner (or any visitor) is on a sub-page — an image detail page, the upload form, or any future page — and wants to return to the main image grid. Clicking the application name in the header takes them there immediately, without needing the browser back button or a dedicated navigation link.
**Why this priority**: The header title is always visible on every page and is the most natural home-navigation affordance. Making it functional costs almost nothing and benefits every session.
**Independent Test**: Open any sub-page (e.g., an image detail page). Click the application title in the header. Confirm the image grid loads. Works identically whether logged in or not.
**Acceptance Scenarios**:
1. **Given** the user is on any page other than the grid, **When** they click the application title in the header, **Then** they are taken to the image grid view.
2. **Given** the user is already on the grid view, **When** they click the application title, **Then** the page either reloads the grid or stays on it — no error, no blank page.
3. **Given** the user is not logged in and is on a public detail page, **When** they click the application title, **Then** they are taken to the grid (which is publicly visible) without being redirected to login.
---
### User Story 2 — Sign Out Lands on Grid (Priority: P1)
The owner signs out of the application and is returned to the image grid rather than the login page. Since the grid is publicly accessible, there is no need to force a redirect to login — the owner can choose to sign back in if they wish.
**Why this priority**: Sending a signed-out user to the login page is unnecessary friction for a personal tool where the grid content is public. It also makes the sign-out flow feel punitive rather than neutral.
**Independent Test**: Log in and sign out from the header. Confirm the image grid is shown, not the login form. Confirm the grid shows images in read-only mode (no write controls).
**Acceptance Scenarios**:
1. **Given** the user is signed in, **When** they click the sign-out control, **Then** their session ends and they are taken to the image grid view.
2. **Given** the user has landed on the grid after signing out, **When** they view the page, **Then** tag-edit and delete controls are not shown (consistent with unauthenticated behaviour already in place).
3. **Given** the user signs out from a sub-page (e.g., detail page), **When** the sign-out completes, **Then** they are taken to the grid — not to the page they were on, and not to the login form.
---
### Edge Cases
- What happens if the grid is unavailable when the user clicks the title? The navigation attempt is made; any existing error-state handling on the grid covers this.
- What if a future page introduces an auth-required route? The header title links to the grid unconditionally; auth guards on specific routes handle their own redirects independently.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The application title displayed in the persistent header MUST be a navigable link that takes the user to the image grid view from any page in the application.
- **FR-002**: The title link MUST be accessible to both authenticated and unauthenticated users; it MUST NOT trigger a login redirect.
- **FR-003**: After a user successfully signs out, the application MUST navigate them to the image grid view.
- **FR-004**: The sign-out destination MUST be the grid view regardless of which page the user was on when they signed out.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: From any page, the image grid is reachable in exactly one click via the header title — no intermediate pages or redirects.
- **SC-002**: After signing out, the user sees the image grid (not the login page) in 100% of sign-out flows.
- **SC-003**: The title link functions correctly for both authenticated and unauthenticated sessions — verified across the grid, detail, and upload pages.
## Assumptions
- The image grid at the root URL is publicly accessible without authentication (confirmed: existing behaviour shows images to unauthenticated visitors).
- The application title ("Reactbin") is already rendered as a text element in the persistent header from the UI polish work; this spec adds navigation behaviour to it, not a new visual element.
- No change is made to the login redirect behaviour for protected routes (e.g., navigating directly to `/upload` while logged out still redirects to login as before).
- The sign-out action clears the session as already implemented; only the post-sign-out destination changes.

View File

@@ -0,0 +1,15 @@
# Tasks: Header Navigation & Sign-Out Destination
## Phase 1: Tests (TDD — write first)
- [X] T001 Add component tests for header title routerLink to `/` and sign-out navigation to `/` in `ui/src/app/app.component.spec.ts`
## Phase 2: Implementation
- [X] T002 Wrap the `.app-name` span in a `routerLink="/"` anchor in `ui/src/app/app.component.ts`
- [X] T003 Change `onLogout()` navigation target from `/login` to `/` in `ui/src/app/app.component.ts`
## Phase 3: Validation
- [X] T004 Run `ng lint` in `ui/` — zero violations
- [X] T005 Run `ng build` in `ui/` — zero errors

View File

@@ -63,7 +63,7 @@ describe('AppComponent', () => {
expect(btn).toBeNull();
});
it('onLogout calls auth.logout and navigates to /login', () => {
it('onLogout calls auth.logout and navigates to / (grid)', () => {
authSpy.isAuthenticated.and.returnValue(true);
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
@@ -71,7 +71,16 @@ describe('AppComponent', () => {
spyOn(router, 'navigate');
fixture.componentInstance.onLogout();
expect(authSpy.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/login']);
expect(router.navigate).toHaveBeenCalledWith(['/']);
});
it('header app-name is a link to /', () => {
authSpy.isAuthenticated.and.returnValue(false);
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const link = (fixture.nativeElement as HTMLElement).querySelector('a.app-name') as HTMLAnchorElement;
expect(link).not.toBeNull();
expect(link.getAttribute('href')).toBe('/');
});
it('header height is 48px', () => {

View File

@@ -1,15 +1,15 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router, RouterOutlet } from '@angular/router';
import { Router, RouterLink, RouterOutlet } from '@angular/router';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet],
imports: [CommonModule, RouterLink, RouterOutlet],
template: `
<header class="app-header">
<span class="app-name">Reactbin</span>
<a routerLink="/" class="app-name">Reactbin</a>
<button *ngIf="auth.isAuthenticated()" class="logout-btn" (click)="onLogout()">Sign out</button>
</header>
<router-outlet />
@@ -25,7 +25,7 @@ import { AuthService } from './auth/auth.service';
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.app-name { font-weight: 600; font-size: 1rem; color: var(--text); letter-spacing: 0.02em; }
.app-name { font-weight: 600; font-size: 1rem; color: var(--text); letter-spacing: 0.02em; text-decoration: none; }
.logout-btn {
background: none;
border: 1px solid var(--border);
@@ -46,6 +46,6 @@ export class AppComponent {
onLogout(): void {
this.auth.logout();
this.router.navigate(['/login']);
this.router.navigate(['/']);
}
}