feat: dashboard home — "resume last viewed document" strip #144

Closed
opened 2026-03-28 11:39:52 +01:00 by marcel · 1 comment
Owner

Context

The home page (/) is being transformed into a dashboard (see related dashboard epic). One of the planned widgets is a "resume last viewed document" strip — a full-width, tappable element placed between the search bar and the main widget grid.

This feature requires no backend changes. It is implemented entirely with localStorage.


Placement

[ Search bar ]
[ ↩ Resume: Großmutters Brief, 1943  →  ]   ← this strip (conditional)
[ Mentions ]  [ Needs Metadata ]
[ Recently Added ]

The strip sits between the search bar and the dashboard widget grid. It is only rendered when a lastViewedDocument entry exists in localStorage. If absent, nothing is shown — no empty state, no placeholder.


Behaviour

  • Write: When the user visits any document detail page (/documents/[id]), store the document in localStorage:

    localStorage.setItem('lastViewedDocument', JSON.stringify({ id, title, documentDate }));
    

    Write on page load (in onMount or a $effect), not on navigation away.

  • Read: On the home page (/), read the entry in onMount and render the strip if present.

  • Suppression: Hide the strip when any search filter is active (same condition that hides all dashboard widgets). The strip is a dashboard element, not a search result.

  • Stale entry: No expiry needed for v1. The entry persists until overwritten by a newer document visit. This is intentional — if the user hasn't visited any document since their last session, they still see the resume prompt.

  • Edge case — document in "Recently Added": Do not suppress the strip if the document also appears in the Recently Added list. The strip is contextual ("you were specifically here"), not just chronological.


Visual Spec

Property Value
Width Full-width (w-full)
Height py-3 px-4 — effective ~48px, touch target ✓ (≥44px)
Background bg-surface
Border border border-line rounded-sm
Left content Clock icon (16×16px, text-ink-3, aria-hidden) + label "Zuletzt angesehen" (text-xs font-bold uppercase tracking-widest text-ink-3) + document title (text-sm font-medium text-ink, truncated with truncate)
Right content arrow (text-ink-3)
Hover state border-accent, title color → text-primary
Focus state focus-visible:ring-2 focus-visible:ring-accent — never outline: none without replacement
Dark mode All tokens are semantic (bg-surface, text-ink, etc.) — no hardcoded colors needed

The visual weight must be lighter than the widget cards — this is service information, not a primary action.


Accessibility

  • The <a> element wraps the entire strip — full touch target, keyboard navigable
  • aria-label: "Zuletzt angesehen: [document title]" on the link element
  • Clock icon: aria-hidden="true"
  • Title text must not fall below 14px (renders at text-sm = 14px — acceptable; prefer text-base if surrounding layout permits)

Implementation Checklist

  • Write lastViewedDocument to localStorage in /documents/[id]/+page.svelte on mount
  • Read and render the strip in +page.svelte (home) — conditional on no active filters
  • Strip hidden when any search filter is active
  • Touch target ≥ 44px verified on 320px viewport
  • Works in dark mode (semantic tokens only)
  • aria-label present on the link
  • No empty state rendered when localStorage entry is absent
## Context The home page (`/`) is being transformed into a dashboard (see related dashboard epic). One of the planned widgets is a **"resume last viewed document"** strip — a full-width, tappable element placed between the search bar and the main widget grid. This feature requires **no backend changes**. It is implemented entirely with `localStorage`. --- ## Placement ``` [ Search bar ] [ ↩ Resume: Großmutters Brief, 1943 → ] ← this strip (conditional) [ Mentions ] [ Needs Metadata ] [ Recently Added ] ``` The strip sits between the search bar and the dashboard widget grid. It is only rendered when a `lastViewedDocument` entry exists in `localStorage`. If absent, nothing is shown — no empty state, no placeholder. --- ## Behaviour - **Write**: When the user visits any document detail page (`/documents/[id]`), store the document in `localStorage`: ```ts localStorage.setItem('lastViewedDocument', JSON.stringify({ id, title, documentDate })); ``` Write on page load (in `onMount` or a `$effect`), not on navigation away. - **Read**: On the home page (`/`), read the entry in `onMount` and render the strip if present. - **Suppression**: Hide the strip when any search filter is active (same condition that hides all dashboard widgets). The strip is a dashboard element, not a search result. - **Stale entry**: No expiry needed for v1. The entry persists until overwritten by a newer document visit. This is intentional — if the user hasn't visited any document since their last session, they still see the resume prompt. - **Edge case — document in "Recently Added"**: Do **not** suppress the strip if the document also appears in the Recently Added list. The strip is contextual ("you were specifically here"), not just chronological. --- ## Visual Spec | Property | Value | |---|---| | Width | Full-width (`w-full`) | | Height | `py-3 px-4` — effective ~48px, touch target ✓ (≥44px) | | Background | `bg-surface` | | Border | `border border-line rounded-sm` | | Left content | Clock icon (16×16px, `text-ink-3`, `aria-hidden`) + label `"Zuletzt angesehen"` (`text-xs font-bold uppercase tracking-widest text-ink-3`) + document title (`text-sm font-medium text-ink`, truncated with `truncate`) | | Right content | `→` arrow (`text-ink-3`) | | Hover state | `border-accent`, title color → `text-primary` | | Focus state | `focus-visible:ring-2 focus-visible:ring-accent` — never `outline: none` without replacement | | Dark mode | All tokens are semantic (`bg-surface`, `text-ink`, etc.) — no hardcoded colors needed | The visual weight must be **lighter than the widget cards** — this is service information, not a primary action. --- ## Accessibility - The `<a>` element wraps the entire strip — full touch target, keyboard navigable - `aria-label`: `"Zuletzt angesehen: [document title]"` on the link element - Clock icon: `aria-hidden="true"` - Title text must not fall below 14px (renders at `text-sm` = 14px — acceptable; prefer `text-base` if surrounding layout permits) --- ## Implementation Checklist - [ ] Write `lastViewedDocument` to `localStorage` in `/documents/[id]/+page.svelte` on mount - [ ] Read and render the strip in `+page.svelte` (home) — conditional on no active filters - [ ] Strip hidden when any search filter is active - [ ] Touch target ≥ 44px verified on 320px viewport - [ ] Works in dark mode (semantic tokens only) - [ ] `aria-label` present on the link - [ ] No empty state rendered when `localStorage` entry is absent
marcel added the featureui labels 2026-03-28 11:39:56 +01:00
Author
Owner

Merged into #145 — the resume strip is already fully specified there (layout, behaviour, visual spec, accessibility, and implementation checklist). All work for this feature will be tracked and implemented under #145.

Merged into #145 — the resume strip is already fully specified there (layout, behaviour, visual spec, accessibility, and implementation checklist). All work for this feature will be tracked and implemented under #145.
Sign in to join this conversation.
No Label feature ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#144