Skip to content

UXS-012: Consumer Home (Learning Hub)

  • Status: Draft
  • Authors: Marko
  • Related PRDs:
  • docs/prd/PRD-042-home.md (this surface) · docs/prd/PRD-038-catalog.md · docs/prd/PRD-039-player.md
  • Related RFCs:
  • docs/rfc/RFC-099-learning-platform-consumer-client.md (§Home & corpus search — behaviour)
  • docs/rfc/RFC-090-* (hybrid search backing the corpus-wide search)
  • docs/rfc/RFC-120-login-first-lure-landing.md (login-first: Home is now authenticated-only; logged-out visitors get the lure landing — see "Access model" below)
  • Related UX specs:
  • docs/uxs/UXS-011-consumer-learning-app.md — the design-system hub: this surface inherits all tokens, typography, and components from UXS-011 (Editorial Bold, dark-primary).
  • Related issue: GitHub #1090
  • Implementation paths: web/learning-player/src/views/HomeView.vue, web/learning-player/src/views/SearchView.vue, web/learning-player/src/components/* (reuses EpisodeCard)

Summary

Home is the app's launch surface — a learning hub, not a list. This spec defines its visual + information-architecture contract: an adaptive hero (resume-first when there's history, search/featured otherwise) with the "Ask your library" corpus search always prominent, plus the supporting sections and the corpus-wide search results surface. Behaviour (the adaptive switch logic, debounce, endpoints) lives in RFC-099.

Principles

  • Orient and resume, don't dump a list. The first glance answers "where was I / what's new", not "here are all episodes" (that's /catalog).
  • The corpus is queryable — make that visible. "Ask your library" is always one glance away; it is the consumer face of the moat (a growing, searchable knowledge corpus).
  • Adaptive, graceful. The hero adapts to state. Sections hide when signed-out or when their index/artifact is absent — but see the state contract below: "hides cleanly when empty" was too blunt a rule and is superseded (#1591).
  • Inherits UXS-011. No new tokens or type scale — Editorial Bold, dark-primary, per-show adaptive accent (the resume hero borrows the player's artwork-derived accent).

Scope

In scope: the Home surface (adaptive hero + sections) and the corpus-wide search results surface (/search). Non-goals: the full catalog (/catalog, UXS-011/PRD-038), the Player (UXS-011/PRD-039), Discovery (PRD-037), the recommendation engine (PRD-041 — Home only renders its output).

Boundary note: static visual contract here; behavioural rules (when the hero switches state, search debounce, data fetching, phasing) live in RFC-099.

Access model — login-first + the logged-out lure landing (RFC-120)

Home is authenticated-only. Under login-first (RFC-120 #2009) a free account is required for all content; the router guard denies by default and sends a logged-out visitor to a dedicated lure landing at /welcome, not to this Home surface. So the "signed-out" language elsewhere in this spec is superseded — Home never renders signed-out now; its remaining state axis is authed with vs without in-progress history (the two hero states below).

The lure landing (web/learning-player/src/views/LandingView.vue, route landing → /welcome). A slim, conversion-focused marketing surface — deliberately not a mirror of Home. Inherits UXS-011 tokens. Regions, top to bottom:

  1. Hero — value line ("Understand any podcast in minutes.") + a short subhead, a primary "Create your free account" CTA and a secondary "Sign in".
  2. Featured this week — a read-only rail of 4 cards, one per distinct show (the shows with the newest episodes, from the anonymous /discover teaser). No action controls (no play/save/queue/follow); a card funnels to signup, threading ?redirect so a shared deep link survives OAuth.
  3. Explore topics — read-only topic chips in the app's chip style (no hashtags), from the anonymous /corpus/trending-topics teaser; also funnel to signup.
  4. How it works — a 3-step strip (Search / Listen / Keep).
  5. Closing CTA — repeat "Create your free account".

The only content a logged-out visitor can see is this curated teaser (server-clamped to ~8 items); everything else requires an account. See RFC-120 for the auth model, edge rules, and teaser allow-list.

Theme support

Inherits UXS-011: dark-primary (MVP), responsive mobile-first (sm/md/lg per UXS-011).

Layout & regions

Mobile-first single column; on lg the rails widen and Home uses the app's max content width. Region order, top to bottom:

  1. Masthead — app identity kicker + title; account/sign-in affordance (per UXS-011 shell).
  2. Adaptive hero (one of two states — see below).
  3. Continue listening — only when not already the hero (auth; hidden otherwise).
  4. What's new — shipped (#1091) as an editorial ranked layout, not a horizontal rail: a featured #01 hero (artwork + gradient + oversized faint numeral) over compact numbered rows (02–06, each with artwork), all on screen; "Browse all →" now opens the Browse hub (/browse?tab=episodes, UXS-011), not the standalone catalog.
  5. Discover — tabbed (#4) — Rising now (momentum) / Trending / Storylines were three stacked rails that made Home very tall; they fold into ONE tabbed switcher (home-discovery, discovery-tab-{key}, rising default). The active tab's label IS the section heading — the rails no longer render a duplicate <h2>. v-show keeps each panel mounted (no refetch on switch).
  6. New in topics & people you follow (#1836) — recent UNHEARD episodes about a followed topic or featuring a followed person (deterministic; no ranking score). Also a Your-Week digest section.
  7. Discover strip — a compact home-browse-nav "Discover" strip (Topics / Storylines / People chips) that deep-link into Discover's own Trends section (/browse?trends=topic/storyline/person), selecting that kind and scrolling it into view. (Renamed from the old "Browse topics/people" links — operator 2026-09-14. They then pointed at a standalone /trends page, which was a thinner second copy of a section /browse already renders; that page is deleted — operator 2026-09-18.)
  8. Recommended for you — shipped as a no-scroll responsive grid**; hidden when no signal.
  9. Featured / spotlight — folded into What's-new as the #01 hero (no separate block).

("Your shows" — a grid of followed podcasts — was removed from Home operator 2026-09-14: the shows you follow already live in Library › Following, so a second copy on Home was redundant.)

Adaptive hero — the two states

  • Resume state (signed-in and has in-progress history): the hero is a large Continue card — artwork-derived background (per-show adaptive accent, contrast-clamped per UXS-011), episode title, show, a progress rule (12:04 / 48:00 · 36 min left), and a primary resume control. The "Ask your library" search bar sits prominently directly below the hero.
  • Discover state (authed, no in-progress history — signed-out no longer reaches Home, see Access model): the hero leads with "Ask your library" (kicker + a short value line + a large search input + a few example query chips) and a Featured spotlight episode. No empty "Continue" card is ever shown.

In both states the search entry is visually prominent (in or immediately under the hero).

  • A query field (carries the Home query) + results across the whole library. Shipped (#1091): results are grouped by source episode (ranked by best hit); each episode header shows an artwork thumbnail + title + show + match count, and each passage is labelled by kind (Insight / Transcript / Topic). A ▶ "Play from m:ss" control appears only when the passage carries a real timestamp (opens the Player there) — otherwise the header opens the episode. Bare topic-term matches are de-emphasised (muted italic).
  • No generated prose (D6) — passages are extractive; no disclaimer needed.
  • Empty / no-index: a single muted line ("Search needs the library index") — never a broken panel. No results: "No grounded passages found."

Key states

  • Hero (resume): artwork-derived bg, --lp-accent progress + resume button (accent-foreground).
  • Hero (discover): surface panel, topic-toned kicker, large search input (UXS-011 input), and — below the input — a row of up to four topic chips (#1964). The chips are outlined in --lp-topic/40 with topic text, and tapping one runs that search. Why they exist: the kicker was the ONLY topic-toned element on Home, so a token that means "this is a topic" carried no meaning and read as decoration. The chips give the colour siblings, and they make the hero answerable — it asks you to search across every episode, and used to offer an empty box you had to already know what to type into. Source: getTrendingTopics(), which Home already fetches for the momentum rail (memoised — no extra request). Absent, not stubbed, when the corpus has no velocity data or the call fails: a hero with no chips is fine, one showing an error where its examples belong is not.
  • What's new / Recommended: shipped as no-scroll layouts — What's-new is the ranked hero+rows, Recommended is a responsive grid (the earlier horizontal-rail/CardRail direction was dropped on Home; CardRail remains available for future Catalog use). Hover → overlay.
  • Loading: skeleton hero + skeleton rail cards (surface/border).
  • Empty/degraded: see the state contract below (#1591) — this previously said "sections with no data are omitted", which conflated three different situations. A fully-empty signed-out Home still shows the discover hero (search) + What's new.

Section state contract (#1591)

Every data-backed section distinguishes three states. Collapsing them is what made a cold corpus, a brand-new account and a total API outage render the same page.

State Behaviour
loading Skeleton, in the section's own shape. The header renders — it is what tells the user this content exists before it arrives.
error A message plus a retry. Never silently equal to empty. Styled once, via SectionStatus.vue, so the same class of failure stops looking different in different views.
ready + empty Depends on why it is empty — see below.

The rule for empty: hide when the SYSTEM is empty, render when the USER is.

  • System-empty — nothing to show because the corpus or the user's history has nothing yet, and there is no action available. Hide; an empty shell is noise. Storylines, Trending topics, Trending shows, Momentum, Recommended.
  • User-empty — empty because of an action the user has not taken yet. Render, and the empty state must carry that action — not a description of it, the action itself. "Your Week" shows one first-run line (#1978 replaced the per-section rows) — "fills as you follow shows, topics and people" — carrying the one action that starts it, a link to the Shows index.

A section that merely describes what the user could do is the failure mode this rule exists to prevent: it makes the reader go and find the control it is telling them about.

Known gap: user-empty states currently render indefinitely, so someone who deliberately follows nothing sees the prompt forever. The intended fix is to stop after the first success (first follow, first capture), which needs a per-user preference flag. Not yet built.

  • Search result active/jump: the ▶ mm:ss uses --lp-accent; focus ring per UXS-011.

Home rails — the named sections (documented 2026-09-03)

Each rail below rendered on Home with automation but no design spec. They share one contract, and it is the reason they can be listed together: a rail that has nothing to show omits itself cleanly. No empty panel, no skeleton that never resolves, no "0 results" row taking up space to say nothing happened. SectionStatus (#1591) owns the loading/empty/error triad so a section header cannot outlive its content.

YourWeek — the personal digest, in-app

The same rollup the weekly email sends (new-in-follows + new-in-interests + trending-in-your-corpus), served live and decoupled from email consent. The email's revisit section is deliberately NOT shown here (2026-09-30): this block is "what's new", and due highlights have their own Home section, RevisitRail ("Highlights to revisit"), with the reviewed / stop / unsave actions that belong to them — showing them in both put the same highlights on Home twice. The server still sends the section, so the email and the push nudge keep it — turning the email off must never cost the capability; the email is only the edge for someone who does not visit. Two layouts behind a per-user synced preference: compact (one rail of the week's top items) and full (a labelled rail per section), flipped inline with "Show more / Show less". Hidden entirely when signed out or when nothing is due — a digest with nothing in it is not a digest.

KeyVoicesRail — your key voices (wave-G)

The people most present in your corpus (heard∪captured), as a horizontal rail of avatar chips linking to each person's card — the per-USER flavor of "key voices" (prominence, not clustering; the per-topic flavor lives on the topic card). Ranked by how many of your episodes each appears in. Authenticated-only and self-hiding: signed out, or with no graph-carrying listening yet, the rail omits itself (a rail is a claim). KG-grounded and deterministic — zero external data.

MomentumRail — what is moving in the corpus (RFC-103)

Topics and people whose recent activity is rising, as chips with a follow affordance. Corpus-wide, not personal: it answers "what is happening" where the interest rails answer "what are you into". The operator-facing global view of the same signal lives on the gi-kg-viewer Dashboard, which is why the two must not drift in vocabulary.

TrendingTopics — trend chips with sparklines

Topic chips carrying a small spark of their recent trajectory. The spark is deliberately unlabelled: it conveys direction, and putting numbers on a 12-point series invites reading precision that the underlying window does not support.

TrendingShowsRail — shows, not episodes

The show-level sibling of the trending rails, so a listener can follow a source rather than a single episode. Cards carry artwork + title; following is the primary action.

Storylines rail → StorylineView

A storyline is a cluster of episodes that continue one thread across shows and time. The Home rail is the entry point; the chip's follow control writes the same interest token the picker does, so a storyline followed here appears in Your Week without a second concept. Opening a chip now navigates to the full-page StorylineView (F4.5) — it replaced the old half-screen StorylineCard bottom sheet, which was deleted.

Themes → ThemeView

A theme is a set of topics that MEAN the same thing (cosine similarity over topic embeddings, tc:), as against a storyline, which is a set of topics that keep coming up TOGETHER (co-occurrence, thc:). Both are groupings over topics rather than entities, so ThemeView is modelled on StorylineView and differs only where the idea differs — the member heading reads "Topics that mean the same thing" against the storyline's "Topics discussed together". That sentence is the whole distinction, and making it is the page's job.

ThemeView exists because a theme cannot be shown on the topic page. The entity card builds from a topic NODE matched by id, and a theme is never a node on an episode, so a tc: id routed to /topic/:id rendered an empty page under a "TOPIC" eyebrow. /theme/:id carries the theme's REAL id — unlike /storyline/:id, which takes an anchor topic because no storyline endpoint exists — so a theme link survives its biggest member changing.

Reaching it from a topic (ThemeCard, ec-theme). The topic card announced one of its two groupings and stayed silent about the other: cluster_id / cluster_label / cluster_size had been on the payload since the card existed and nothing rendered them, so the theme appeared only as "similar topics" chips — its MEMBERS, without ever naming the thing they are members of — while the storyline had a named "Part of a storyline" link all along. A reader therefore met one grouping as an object and the other as a loose chip list, which is also why the two ideas were hard to tell apart.

Part of a theme now sits directly above the storyline link so the pair reads as two different claims about the same topic — "means the same thing" against "keeps coming up together". It opens ThemeCard, a teleported sheet wrapping ThemeView embedded, mirroring StorylineCard exactly: both groupings open with the same gesture, and the sheet is not a route because inside the Knowledge Panel (a top-layer showModal() dialog) a router.push changes the page UNDERNEATH and the tap reads as dead — the defect that made the storyline link route-free in the first place.

Its episode list is the de-duplicated union across every member, and that merge is the reason the page is worth having: a similarity cluster exists precisely because searching one member misses the others, so showing one member's episodes would not answer the question the grouping poses. Measured on the v3 fixture: 8 members, 84 episodes with overlap, 40 distinct — where the largest single member carries 30.

What changed (MemberTrendBadge). A grouping is not a static set — topics join it, carry it for a while, and drop out — and the member list said none of that: the same words in the same order whether a topic had been there since the first episode or arrived last month. Each member now carries first_seen, last_seen and a trend, and a badge renders only when the member actually moved: new, growing, fading, gone. steady renders nothing, which is most members most of the time — a badge on every row is a badge that says nothing.

The split is the grouping's OWN median episode date, never a fixed window: a "last 12 months" rule would brand every member of a young corpus new. Arriving (new/growing) takes the accent colour, leaving (fading/gone) the muted one — a direction, not a judgement. A topic leaving a storyline is how a storyline moves on.

This applies to BOTH groupings, unlike the anchor and the co-occurrence pair: "has this member's presence changed" is a question about any set over time, while those two describe co-occurrence, which is what a storyline is made of and what a theme explicitly is not.

Not yet: Save (heart) and notes. FavoriteKind and NoteTarget are server-validated contracts and neither admits theme, so those controls would offer an action the API rejects. Follow works today, because a theme is followed by its tc: token and the interests store already carries it.

Components

  • EpisodeCard (UXS-011) is reused on Catalog + search-result episodes, not Home; Home's What's-new hero (#01) + numbered rows and the Continue card are bespoke layouts (the EpisodeCard is the clean-lede + ✦ insights-popover card).
  • Search bar: pill input (UXS-011 input tokens), search icon, example chips (topic toned).
  • Continue hero card: artwork bg + progress rule + circular resume button (player transport styling, UXS-011).
  • Search result card: passage text (surface-foreground), source line (muted + accent show link), ▶ mm:ss (accent, font-mono tabular).

Accessibility

  • Search input has a visible/programmatic label; example chips are buttons with names.
  • Rails are keyboard-scrollable and not focus-traps; each card is a link with an accessible name.
  • One h1 (Home), section h2/headings in order; the adaptive hero swap preserves heading order.
  • ▶ mm:ss controls have accessible names ("Play from 12:04 in ").
  • Respects prefers-reduced-motion (no rail auto-advance; instant scroll). WCAG 2.1 AA contrast (inherits UXS-011 tokens; per-show accent contrast-clamped).

Tunable parameters

Parameter Current Status Notes
Hero switch rule resume when in-progress history exists Open exact "in-progress" threshold → RFC-099
Rail length (What's new / Recommended) ~6 Open perf vs richness
Example search chips derived/static Open could be topic-driven later
Tokens / type inherit UXS-011 Frozen do not fork the design system

Acceptance criteria

  • [ ] Home uses UXS-011 tokens only (no new hex/scale; no design-system fork)
  • [ ] Adaptive hero: resume-state when history exists, discover-state otherwise; never an empty Continue card; search prominent in both states
  • [ ] Every section hides cleanly when empty / signed-out / no index (no broken panels)
  • [ ] Corpus search results show source episode + speaker + working jump-to-moment; extractive (no generated prose); graceful empty/no-index states
  • [ ] Rails keyboard-operable; one h1; headings ordered; visible focus; reduced-motion honoured
  • [ ] All copy via vue-i18n (no hard-coded strings); RTL-ready
  • [ ] Mobile-first; perf budget on the worst common device (per UXS-011)

Visual references

docs/wip/player/mockups/home-{a-search-first,b-resume-first}.{html,png} — the two explored directions. Decision: the adaptive hero (resume-state borrows A's prominent search; both states keep "Ask your library" one glance away). WIP aids, not shipped assets.

Home components (governed here)

Home-surface components this document governs, named so the surface-map guard can tie each rendered piece to its design home:

  • YourWeekCard — one "Your Week" digest item: title-forward, with an artwork backdrop + gradient scrim where available; links to the episode. (It was quote-forward for revisit items and linked to the captured moment until 2026-09-30, when Home stopped showing the revisit section.)
  • TrendingSparkChips — the trending topics as compact rows (theme-colour swatch, label, ×velocity, a mini sparkline), storylines grouped by hue, collapsed to top-N on mobile.
  • TrendWindowTabs — the segmented 1M·3M·6M·1Y control (RFC-103 R2) that picks the window over which trending velocity is measured (default 3M).
  • DiscoveryList — the one shared list for a single entity kind (topic / storyline / person), rendered both on Home's tabbed discovery switcher and on the full Trends page. Each row carries a label, optional subtitle, trend-hued sparkline, a trailing metric that tracks the active sort, and a discovery-follow toggle. Sourced from GET /api/app/trending (EWMA velocity + volume). Replaced MomentumRail, TrendingTopics, and Storylines rails.
  • DiscoveryExplorer — the shared section (discovery-explorer) wrapping the tabbed DiscoveryList (topics/storylines/people) with the Rising⇄Trending sort (discovery-sort) and Corpus⇄Mine scope (home-trending-scope) switches. Used by BOTH Home (capped at 5 rows, with the inline discovery-expand "Show N more") and the Discover page (/browse, capped at 10, with a per-kind discovery-see-all "See all →" link to /browse?trends={kind}, which selects that kind in this same explorer and scrolls it into view). Extracted from HomeView so Home and Discover cannot drift (operator 2026-09-14, replaced the top-3 DiscoveryDashboard).
  • TrendsView — DELETED (operator 2026-09-18). It was a standalone /trends page carrying the same three kind tabs as the explorer above, reached from that explorer's "See all →". Tapping it left the hub for a thinner copy of a section the hub already rendered, which the operator reported as "small pages that should not exist". /browse?trends={kind} replaces it: same section, kind selected, scrolled into view.

Revision history

Date Change
2026-06-24 Initial draft — adaptive hero (resume/discover) + corpus search surface
2026-08-26 Mobile pass: Discover folded into tabs (#4, discovery-tab-{key}); "New in topics & people you follow" (#1836) + 4th Your-Week first-run row; What's-new "Browse all" + browse chips deep-link the Browse hub (#14)
2026-09-06 Login-first (RFC-120 #2009): Home is now authed-only; logged-out visitors get the new lure landing (LandingView, /welcome). Added the Access-model section; superseded the stale signed-out language