← PROTOTYPES

Sentiment 4 — voices you can read at a glance

Lane M30 · 23 September 2026 · branch hub/sentiment-4-20260923 · built from the release build b6913cb · nothing deployed, nothing pushed.

The short version. News, YouTube and X now report the same five numbers over a stated window, side by side, so you can tell which voice is worth listening to. The table of blended channels has become a picture: a lean bar with the spread behind it, and one coloured chip per ticker. The keyword tracker now says which words carried which ticker. The word list learned the language people actually type — slang, abbreviations, phrases and emoji — and every new entry says why it is there. And the room now measures market breadth over all 364 of our names, which says out loud what you noticed today: this rally is narrow.

1 · One measuring stick

You said: “News: 31 scored, 21 up, 7 down — it’s very inconsistent the way you’re measuring across platforms.” Every voice now answers the same questions:

This is what it printed when the screenshot was taken (23 Sep, 23:2xZ):

voicewindowreadscored% bull% bearnetnewest
NEWS24 hours2,000669 · 33%54%36%+188m
YOUTUBE7 days745359 · 48%49%38%+113h
X24 hours1,071431 · 40%57%29%+281h

Read that way: X is the most bullish voice today and the freshest; news is the widest but only a third of its headlines carry a scoring word; YouTube is a week-long view, so it moves slowest. A voice with fewer than 30 scored items is marked thin on the page — a thin voice is a small sample, not a quiet market.

the measuring stick table at 1680
After, 1680 wide: the three voices on one stick, each with its own lean bar (green right of centre is bullish). The old per-input table is above it, now carrying the breadth rows.

2 · Voices, seen instead of listed

You said the blended table was “a data dump… very not nice to see”. It is now a picture: each voice gets a lean bar with a grey band showing how far apart the channels behind it actually are, and each ticker is a chip — colour is its lean, the number beside it is how many channels or handles carried it, and the tiny two-colour bar is the split inside it.

before: the blended table
Before (release build b6913cb): a table of TICKER / BLENDED LEAN / SPLIT / CHANNELS / CLIPS.
after: the chips
After: the same readings as coloured chips with the spread drawn above them.

3 · Keywords, with their ticker

You asked for “a keyword tracker associated to a ticker that was mentioned with”. The rail now carries KEYWORDS · BY TICKER: today first, the week behind it when today is quiet. From the same screenshot:

SPY — calls ×19, bullish ×10, support ×8, rally ×6 / resistance ×16, lower ×13, puts ×13, shorts ×11 · 86 today
QQQ — calls ×12, rally ×8, breakout ×7, bullish ×7 / resistance ×12, puts ×10, lower ×7, shorts ×6 · 80 today
NVDA — buy ×5, breakout ×3, higher ×3, ath ×2 / lower ×5, bearish ×3 · 44 today

The words come from X posts (by their $TAG) and YouTube clips (by their ticker column), counted after negation, so a flipped “bullish” is counted on the bearish side — the same rule the score itself uses. News headlines are not in this card, and the page says so: the stored news rows keep the score and the counts, not the headline text, so their words cannot be named per ticker yet.

keywords by ticker on a phone
Phone, 390 wide: the stick keeps its numbers (window and age columns drop away) and the keyword card reads as a sentence per ticker.

4 · The language people actually use

The old list was plain English — “bullish”, “crash”, “upgrade”. The people on these channels do not talk like that. Version sc-social-v2 keeps every old word exactly as it was (so no old number moves for an invisible reason) and adds 62 entries: slang, abbreviations, two- and three-word phrases, and emoji. Every entry carries its polarity and a note saying why it is on the list, printed on the HOW IT IS BUILT page so you can argue with it.

entrykindreads aswhy it is there
calls printingphrasebullishupside options making money — the clearest bullish phrase in this language
puts printingphrasebearishbearish, although it contains “printing”
buy the dipphrasebullishbullish even though it contains a fall
short squeezephrasebullishthe words apart would read bearish
dead cat bouncephrasebearisha bounce the speaker does not believe
rekt · bagholder · ngmislangbearishwrecked; stuck holding a loser; not gonna make it
LFG · tendies · printingslangbullishexcitement; profits; a position making money now
🚀 🐂 📈 💎 🔥emojibullishthe most common marks in this feed — the old scanner threw them away with the punctuation
🐻 📉 💀 🩸 😭emojibearishthe same, on the other side
the word list and the before/after card
HOW IT IS BUILT, 1680 wide: the versioned list — 78 plain words unchanged, plus 62 added (35 slang, 14 phrases, 13 emoji), each with its note — and beneath it the before/after card running on the posts in the current window.
What the before/after card actually caught tonight — read this one carefully. Two of the four posts it listed are false positives, and they are the honest cost of counting emoji: Emoji buy coverage and pay for it in noise, because 📈 marks any number going up. That is a real trade-off, it is visible on the page rather than buried, and it is the first decision for you at the end of this note.

A phrase is matched before its single words, so “buy the dip” is one bullish reading, not a “buy” beside a “dip”, and “puts printing” is bearish rather than a bullish “printing”. The HOW IT IS BUILT page also carries a BEFORE AND AFTER · REAL POSTS card: posts from the window on screen that read differently now, with the new words named. If nothing changed in that window, it says that instead of hiding it.

5 · Breadth — the whole list, not the index

You said: “This rally was really low breadth — we need to start considering market breadth more in our mix.” There are now four breadth readings, each on its own row with its own source and age, and none of them folded into a secret composite. Measured on session 2026-09-23 over all 364 names:

readingwhat it saidwhere it comes from
Equal weight vs the indexRSP − SPY over 20 days: −5.0 points — the average stock is well behind the indexchart API daily bars, live in the page
Above the 50-day average134 of 364 · 36.8%measured snapshot, one daily series per name
Above the 200-day average190 of 361 · 52.6%same snapshot
52-week highs vs lows5 highs (ABBV, AMD, CRWD, DE, TMO) against 23 lows — 18% of the extremes are highssame snapshot; today’s high or low against the 252 sessions before it

Together with the older row — 105 of 363 names up on the day, 29% advancing — that is a plain statement of what you saw: the index can be held up by a handful of names while most of the list is below its own 50-day average.

How the snapshot works. Breadth needs a year of daily bars for every name, which is 364 requests — far too much for a page load. scripts/breadth-snapshot.mjs reads them from the chart API (about 20 seconds, no other provider, nothing written to any price table) and writes data/breadth/latest.json plus a one-row-per-session history.json. The page reads that file and always prints when it was measured, so a stale reading looks stale instead of looking current. Today’s run is the first, so the history has one row; each later run adds one.

6 · Is the X feed current?

The brief said the collector’s heartbeat read “error” at 18:32Z. Here is what actually happened, from the collector’s own files on this MacBook:

So there was nothing to fix in the collector, and I did not change it. What was missing was that the room never showed any of this, so a stalled pass was invisible until the numbers looked wrong. The rail now carries IS THE X FEED CURRENT? — the newest post, its age, how many posts fell in the window, how many carried a scoring word, and the collector’s schedule. If the newest post is more than four hours old it says the collector has probably missed a pass.

Where every number comes from

numbersource
News read / scored / bull / bearthe stored per-headline rows (or the same scoring done in the browser when the table is absent), Loughran-McDonald plus headline verbs, 24-hour window
YouTube read / scored / bull / bearyoutube_videos ticker-tagged clips, last 7 days, scored on their titles and descriptions
X read / scored / bull / bearthe collector’s published feed at /api/xfeed, posts from the last 24 hours
Ticker chips and their splitsthe same items, one channel or handle = one vote, whatever its volume
Keywords per tickerthe same items again, words counted after negation, credited to every ticker the item mentioned
Breadth (50-day, 200-day, 52-week)data/breadth/latest.json, measured from chart API daily bars by scripts/breadth-snapshot.mjs
RSP vs SPYchart API daily bars, read live by the page
The published gauges (CNN, crypto, VIX, put/call)unchanged from before this lane

What could be wrong

What I did not do

Proof

Screenshots were taken in a real headless Chromium at 1680×1050 and 390×844, against this branch served from disk. The chart API only accepts https://scintillahub.ai as an origin, so both it and /api/xfeed were intercepted and re-issued from node with that origin header — that is the only thing relaxed; no page code was stubbed and no data was faked. 27 requests were proxied, none failed, and the page reported no script errors.

the room on a phone
Phone, 390 wide: the gauges stack, the readings table keeps its numbers, and nothing runs off the side.
the room on a phone before
The same phone view on the release build, for comparison.

Tests: node --test tests/ — 468 tests, 464 pass, 2 fail, and both are the failures that were already failing on production before this lane (“every place this surface prints an age uses the one function” and xfeed-publish). The new file tests/sentiment-stick.test.mjs adds 10 tests covering the measuring stick, the phrases and emoji, the per-ticker keywords, the spread, and the breadth snapshot’s internal consistency.