SPEC-0004: Application Views and Routing
Overview
This specification defines the complete set of application routes, views (pages), shared layout components, and HTMX interaction patterns for joe-links. It covers the slug resolver (the core go-link redirect), the authenticated user dashboard, link CRUD views, tag browsing, admin views, and the landing page.
See 📝 ADR-0007 (Application Views and Routing), 📝 ADR-0001 (Technology Stack), SPEC-0001 (Core Web App), SPEC-0002 (Data Model), SPEC-0003 (UI Theming).
Requirements
Requirement: Route Registration and Priority
The chi router MUST register all named routes before the catch-all slug resolver (GET /{slug}). The following path prefixes MUST be reserved and MUST take precedence over slug resolution: auth, static, dashboard, admin. Route-level authorization MUST be enforced via chi middleware groups, not inside individual handlers.
Scenario: Reserved Prefix Takes Precedence
- WHEN a request arrives at
/dashboard - THEN the dashboard handler MUST be invoked; the slug resolver MUST NOT be invoked
Scenario: Static Asset Served
- WHEN a request arrives at
/static/css/app.css - THEN the embedded static file server MUST respond with the CSS; the slug resolver MUST NOT be invoked
Scenario: Slug Resolved After All Named Routes
- WHEN a request arrives at
/foobarandfoobaris not a registered named route - THEN the slug resolver MUST be invoked
Requirement: Landing Page (GET /)
The landing page MUST be served at GET / without authentication. If the requesting user has a valid session, the server MUST redirect them to /dashboard. If unauthenticated, the page MUST render a hero section explaining go-links, a "Sign in" call-to-action button linking to /auth/login, and a brief description of the service.
Scenario: Unauthenticated Root Visit
- WHEN an unauthenticated user visits
/ - THEN the landing page MUST be rendered with a sign-in CTA
Scenario: Authenticated Root Visit
- WHEN an authenticated user visits
/ - THEN the server MUST issue a
302 Foundredirect to/dashboard
Requirement: User Dashboard (GET /dashboard)
The dashboard MUST be served at GET /dashboard and MUST require authentication. It MUST display all links the authenticated user owns or co-owns, a search/filter input, a tag filter control, and a "New Link" button. Links MUST be displayed with their slug, title (or URL if no title), description excerpt, tag chips, and edit/delete action controls. An empty state MUST be shown when the user has no links, with a prominent "Create your first link" prompt.
Scenario: Dashboard Shows Owned Links
- WHEN an authenticated user with existing links visits
/dashboard - THEN all links where the user appears in
link_ownersMUST be listed
Scenario: Dashboard Empty State
- WHEN an authenticated user with no links visits
/dashboard - THEN a friendly empty state with a link-creation CTA MUST be rendered
Scenario: Dashboard Search
- WHEN an authenticated user types in the search field (HTMX
hx-getwith debounce) - THEN the link list MUST be replaced with a filtered fragment matching the query against slug, title, and description
Scenario: Dashboard Tag Filter
- WHEN an authenticated user clicks a tag chip in the filter row
- THEN the link list MUST be replaced with links tagged with the selected tag
Requirement: New Link Form (GET /dashboard/links/new and POST /dashboard/links)
The new link form MUST be accessible via GET /dashboard/links/new (full-page fallback) and as an HTMX modal (hx-get="/dashboard/links/new" hx-target="#modal"). The form MUST include: slug (required, live-validated), URL (required), title (optional), description (optional), tags (optional, with autocomplete). Submission MUST go to POST /dashboard/links. On success, the browser MUST be redirected to /dashboard with a success toast. On error, the form MUST be re-rendered with inline validation messages.
Scenario: Successful Link Creation
- WHEN an authenticated user submits a valid slug and URL
- THEN the link MUST be created and the user MUST be redirected to
/dashboard
Scenario: Live Slug Validation
- WHEN an authenticated user types in the slug field
- THEN an HTMX request MUST fire (debounced 300ms) to
GET /dashboard/links/validate-slug?slug=...and render an inline availability indicator
Scenario: Slug Taken — Inline Error
- WHEN live slug validation returns a taken slug
- THEN a red inline indicator MUST appear beside the slug field without submitting the form
Scenario: Form Validation Error on Submit
- WHEN a user submits the new link form with an invalid slug or missing required fields
- THEN the form MUST be re-rendered with inline error messages for each invalid field
Scenario: Tag Autocomplete
- WHEN a user types in the tag input
- THEN an HTMX request MUST fire (debounced 200ms) to
GET /dashboard/tags/suggest?q=...and render a dropdown of matching tags
Requirement: Link Detail View (GET /dashboard/links/{id})
A read-only detail page MUST be served at GET /dashboard/links/{id} for authenticated users who are owners or admins. It MUST display the full slug, URL (clickable), title, description, tags, and the list of co-owners. A copy button MUST copy the full go-link URL to the clipboard. Edit and Delete action buttons MUST be rendered for owners and admins.
Scenario: Detail View for Owner
- WHEN an authenticated owner visits
/dashboard/links/{id} - THEN the full link detail MUST be rendered with edit and delete controls visible
Scenario: Detail View Forbidden for Non-Owner
- WHEN a non-owner non-admin user visits
/dashboard/links/{id} - THEN the server MUST return
403 Forbidden
Scenario: Copy Go-Link URL
- WHEN a user clicks the copy button
- THEN
navigator.clipboard.writeText(fullGoLinkURL)MUST be invoked and a success toast MUST appear
Requirement: Edit Link Form (GET /dashboard/links/{id}/edit and PUT /dashboard/links/{id})
The edit form MUST be served at GET /dashboard/links/{id}/edit for owners and admins. The slug field MUST be rendered as read-only. All other fields (URL, title, description, tags) MUST be editable. Submission MUST go to PUT /dashboard/links/{id}. On success, the browser MUST be redirected to the link's detail page.