# Design note — second visual pass

**Dated 2026-08-04. Written before the code, per the provenance practice in
[ROADMAP.md](../../ROADMAP.md#proving-independence).**

The first pass made the surfaces modern — cards, radii, depth, rhythm. This one
makes the *content* modern, which is the part that was actually missing.

Direction taken from a theme Ernest linked as a reference; the layout,
palette and everything below are Convoro's own, per the originality rules.

---

## The observation

Rounding the corners on an empty row does not make a forum look alive. What
separates a forum that looks designed from one that looks templated is
**information density on the rows people scan**: who was here, when, and what
they said. Convoro's forum index showed a title, a description and two numbers.
Every mature forum shows the last post, because that is the thing that tells
you whether a place is worth clicking into.

So this pass is mostly about putting real information where there was none, and
only then styling it.

## Decision 1 — avatars are a first-class thing, with a deterministic fallback

`users.avatar` exists and nothing ever rendered it. Now it does.

When there is no image, the fallback is initials on a colour **derived from the
username** rather than the same accent tile for everybody. Two reasons:

1. A wall of identical indigo squares carries no information. A stable colour
   per person makes a thread skimmable — you recognise who is talking before
   you read the name.
2. It is deterministic, so somebody's colour never changes. A fallback that
   shuffled would be worse than one flat colour.

The hue comes from a hash of the username, with saturation and lightness fixed
so every generated colour sits in the same family as the theme rather than
clashing with it. Dark mode gets its own lightness, because a palette tuned for
white behind it is unreadable on black.

## Decision 2 — relative time, with the real one underneath

"3 hours ago" instead of "Aug 4, 2026 12:10am". It is how people read a forum:
the question is *how recent*, not *what date*.

The exact timestamp goes in a `title` and a `<time datetime="…">`, so hovering
still answers precisely and a machine reading the page still gets something
unambiguous. Anything older than about a week falls back to a date, because
"47 days ago" is not information anybody wanted.

## Decision 3 — forum rows carry their last post

Avatar, topic title, and when. This is the single biggest change in the pass.

**It has to be two queries, not two per forum.** The naive version is a query
per row inside the loop, which is fine on a demo with two forums and a problem
on a real board with forty. The controller collects every `last_post_id` and
`last_topic_id` up front and loads them in one go each.

A forum with nothing in it says so, rather than showing an empty column.

## Decision 4 — topic rows get an author and an activity time

Same idea, one level down: the avatar of whoever started it, the reply count as
a chip rather than loose text, and when it last moved.

Replies become the prominent number because it is the one people use to judge a
thread. Views stay, quieter.

## Decision 5 — numbers are designed, not left as text

Counts were loose text sitting at the end of a row: `2 topics  9 posts`. That
is data rendered rather than data presented, and it is the thing Ernest called
out.

Three changes:

**Per-row counts become a stat pair** — the number large and tabular, the label
small and muted beneath it. Tabular figures matter more than they sound: with
proportional digits a column of counts jitters, and a forum list is a column of
counts.

**Replies is the primary number on a topic row**, in a chip. It is the one
people actually judge a thread by. Views stay, quieter, because they are
context rather than a signal.

**The forum index gets a stats band**: topics, posts, members, and who joined
most recently. Every forum has had one of these for twenty years because it
answers the question a first-time visitor has — *is anyone here?* — and it is
four numbers we already store.

The newest member is a person, not a number, so it gets their avatar. That is
the point of the band: it should read as a place with people in it.

## Decision 6 — the header sticks

It is where search, notifications and the account live. Scrolling a long topic
should not mean scrolling back to reach them.

Backdrop blur where it is supported, so content moving underneath reads as
moving underneath rather than being clipped by a solid bar.

## Decision 7 — empty states say what to do

"No topics yet" is a dead end. "No topics yet — start the first one" with the
button right there is a page that helps. Cheap, and it is the difference
between a forum that feels unfinished and one that feels new.

## What this does not do

- **No layout change.** Still one column. A sidebar is a real decision about
  what deserves permanent space, and it is not a styling question.
- **No uploaded avatars.** The column is rendered if it has something in it;
  putting an image there is the attachment system's job and a separate piece.
- **No landing hero.** Worth doing, but it belongs with the page builder that
  already exists rather than being hardcoded into the forum index.
