# Signal — what a topic needs, and which reply answered it

2026-08-11. Port #5, from the Invision application `edsignal`.

## First, a correction

I said this port would bring the Traffic screen with it. It does not. Reading
the source, `edsignal` scores posts and labels topics by what they need —
unanswered, waiting, solved. The empty Traffic screen belongs to `edpulse`,
which is the analytics application. Nothing is lost; the order was picked on
benefit and Signal still earns its place near the front, because it changes
what every topic list looks like.

## The problem

A reader scanning a forum wants to know **where they can help** and **where
they will find an answer**. A topic list tells them how old everything is.

The obvious competitor derives four labels — New, Active, Quiet, Dormant — from
one number: time since the last reply. That is the same information the "last
reply" column already shows, wearing four coats.

## The idea

Two halves, built together because neither is much use alone.

**Score every reply** from signals the community already produced — reactions,
quotes, the author's standing, and a little for substance and position. Then
**label every topic** from what those scores plus the conversation's shape say
about what it needs.

The state that only exists because scoring exists is **Answered**: one reply
stands well clear of the others, so it probably answers the question even
though nobody ever pressed a button. On a forum where nothing is ever marked
solved — which is most forums — that is the whole value.

## The states, in the order they are tested

First match wins, so this order is the editorial judgement.

| | |
|---|---|
| **Settled** | An answer was accepted and the conversation has finished. |
| **Solved** | An answer was accepted. |
| **Answered** | One reply scores well clear of the others. Nobody marked it. |
| **Locked** | Nobody can reply. |
| **Unanswered** | Nobody has replied yet. |
| **Busy** | Replies are still arriving quickly. |
| **Needs a reply** | The person who asked spoke last. |
| **Waiting on asker** | Somebody replied and the asker has not come back. |

**"Settled", not "Dormant".** The same fact, and the opposite impression of the
community that produced it. A finished support thread is not decay.

**Locked sits below the three that report a result** and above the four that
make a promise. Everything under it implies something could happen next, and on
a locked topic all of it is false — worse than useless, because it invites
somebody to help and then shows them a thread they cannot post in. Ernest found
that one on production.

## What carries across unchanged, and why

**Human signals outweigh text signals, heavily.** A live pass over a real forum
with 430 posts, one reaction and nothing ever marked solved found that the only
things moving the score were length and position — so the highest-scoring posts
were automated release announcements, which are long, full of code, and answer
nothing. Length is evidence of effort, not of value. A post with no human
signal is held near the baseline however long it is.

**The standout test is relative, not absolute.** Scores on that same forum sat
in a 1.0–2.5 band. Any fixed threshold would have labelled everything or
nothing. Standing clear of your neighbours means something on a quiet forum and
on a busy one.

**The baseline is 1, not 0.** With a floor of zero, a downvoted post and a post
nobody has read score identically, which throws away the only thing a signed
reaction tells you. Found by a test asserting a downvoted post ranks below an
ignored one — it did not.

**Reactions are summed by value, not counted.** Convoro's `reaction_types` carry
a signed `points`, so a pile-on subtracts. Counting rows would score contempt
the same as praise.

**Explainability is the product.** Every score returns its reasoning next to its
number. An admin who cannot ask "why is this 8.2?" will not trust the score, and
a score nobody trusts changes nobody's behaviour.

**A moved topic gets no label at all.** It is a redirect stub, not a discussion.
Production had four of them wearing "UNANSWERED" — the badge working exactly as
designed against something that was never a question.

## What changes coming to Convoro

| | |
|---|---|
| **Accepted answers do not exist here** | Invision has `topic_answered_pid`; Convoro has a "Solved" *prefix* and nothing more. So this extension adds marking a reply as the answer — the topic's author or a moderator, one reply per topic. Without it, Solved and Settled are unreachable and the strongest human signal is missing. |
| **Its own tables, not core's columns** | The Invision version deliberately wrote into `forums_posts.post_score`, because Invision ships the column and never fills it. Convoro has no such column and an extension has no business adding one to a core table — it could not be taken away again cleanly. `signal_posts` and `signal_topics` are the extension's own and vanish with it. |
| **Dirty rows, not a polling sweep** | Invision's pass polled: find posts whose topic has moved since the watermark. Convoro has events, so a post is flagged when something actually happens to it and the pass only ever looks at flagged rows. When nothing has happened there is no job at all, rather than a job that runs and finds nothing. |
| **Batched at the top of a list, not queried per row** | `forum.topiclist.before` carries the whole `topics` array, so one query primes every badge on the page. A hook that queried per row would be twenty-five queries a page load, and it would have looked fine in testing. |
| **No `Usage` phone-home** | `sources/Usage/Usage.php` does not come across, as with every port. |

## One thing Convoro is missing, found here

**An extension cannot register a console command.** Commands are a hardcoded
list in `Convoro::console()`, and modules boot before the kernel exists. It does
not block this port, because the queue is drained minutely and event-driven work
belongs there anyway. It will block something eventually.

## Scope

In: scoring with reasoning, the eight states, badges on topic lists, an answer
marker on the reply, marking an answer, a backfill, and an admin screen that
shows progress and explains the top-scoring post on the site.

Out, deliberately: no per-forum states, no sorting or filtering a forum by
state, no notifications when a topic goes unanswered. Each is a real feature and
none is needed to prove the idea.
