Skip to content

Color the read-time chip and measure only visible text - #23

Merged
sjmiller609 merged 1 commit into
mainfrom
hypeship/viewer-read-time
Jul 31, 2026
Merged

Color the read-time chip and measure only visible text#23
sjmiller609 merged 1 commit into
mainfrom
hypeship/viewer-read-time

Conversation

@sjmiller609

@sjmiller609 sjmiller609 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #21 (now in main): the read time in the viewer bar becomes a colored chip, and the number counts only the text a reader can actually see on first load.

1. The chip

Read time is now text in a filled box whose weight escalates with the estimate:

bucket fill text
< 5 min kernel-green #81b300 charcoal #212225
5–15 min beige-muted #e1dccf charcoal
> 15 min charcoal #212225 beige #f2f0e7

On brand compliance: the Kernel palette has no status colors — no red, no amber — and gold is explicitly never a background, so a literal ok/warn/error traffic light can't be built from approved tokens. Every value above is from the palette, and the escalation is carried by contrast weight instead of hue. Geometry mirrors the website's Tag (4px radius, hairline border, 4/8px padding). Contrast: beige-muted+charcoal and charcoal+beige are AAA; kernel-green+charcoal is AA, which the brand guide allows for short dense glyphs like this one.

On a dark bar the two darker borders switch to a light tone (paletteVars) — otherwise the grey hairline disappears and the charcoal chip merges into the bar. Fills are literals rather than derived from the sampled doc palette, so the chip holds its contrast whatever colors the doc uses.

Color isn't the only signal: the tooltip spells out the estimate, and past 15 minutes it says why the chip is heavy.

Thresholds live next to the rate constants in lib/docs/reading-time.ts. Exactly 15 minutes is warn; only 16+ is over.

2. Visible-text measurement

The server estimate counts every word in the stored HTML — it can't see that three of four tab panels are hidden by a class defined in a <style> block. The overlay can, so it walks the laid-out DOM, prunes hidden subtrees (display:none, visibility, opacity:0, hidden, aria-hidden, closed <details> beyond its summary, <dialog> without open) plus our own injected chips, counts what survives with the same wpm/CJK rules, and posts jh:readtime.

The shell seeds readMinutes from the SSR value and swaps in the overlay's — so the chip paints immediately and a doc whose overlay never runs keeps the full-text number. Measurement runs on load, at 400ms with the theme sample, and once more at 1500ms; each report supersedes the last, which is what catches a tab UI that only hides its panels after script runs.

The rate constants are duplicated in the overlay (stringified browser JS, can't import server code) and marked as needing to stay in sync — same split as the reaction signature.

Assumption worth confirming: the chip's color follows the number shown, i.e. the visible-text estimate. So a 40-minute doc with most content behind tabs shows a green 4 min read. The alternative is coloring by total text while displaying visible-only.

Testing

  • npm test — 171 passing, including threshold boundaries at 4/5/15/16 and the tooltip copy.
  • npm run build and tsc --noEmit clean. /d/[slug] first-load JS unchanged at 165 kB.
  • Verified in a browser against seeded fixtures, reading the settled DOM over CDP: 600/1600/4000-word docs → 4 min green, 9 min beige, 21 min charcoal; a CSS-tabbed doc 23 → 4; a script-tabbed doc that hides panels 1s after load 23 → 4; a doc with 4000 words in a closed <details> 22 → 3. Chips checked on light and dark docs.
  • The overlay has no unit tests (stringified browser JS), so its half is fixture-verified by hand, not covered by the suite.

Note

Low Risk
UI-only viewer chrome and overlay messaging; main maintenance risk is keeping duplicated read-rate constants in overlay.ts aligned with reading-time.ts.

Overview
Read time now starts from the SSR estimate but the doc iframe overlay re-counts only visible prose (hidden panels, closed <details>, zero-opacity nodes, injected chips/anchors excluded) and sends jh:readtime to replace the bar value once layout settles (load, 400ms, 1500ms).

The chrome read-time chip is restyled with Kernel palette buckets via readTimeLevel / readTimeTitle (ok <5 min, warn 5–15, over >15), including dark-bar border CSS vars and hiding the chip on mobile (<=768px).

lib/docs/reading-time.ts documents the SSR vs overlay split and exports the level/title helpers; WPM/CJK rates are duplicated in the stringified overlay script with an explicit sync comment.

Reviewed by Cursor Bugbot for commit d46dc4a. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
justhtml Ready Ready Preview Jul 31, 2026 3:42pm

The bar's read time becomes a filled chip whose weight escalates with the
estimate — kernel-green under 5 minutes, beige-muted to 15, charcoal past that.
The Kernel palette has no red or amber and gold is never a background, so the
escalation is contrast weight rather than hue; the tooltip carries the same
meaning in words.

The overlay also re-measures the text that is actually visible once the doc has
settled (hidden tab panels, closed <details>, unopened dialogs) and posts
jh:readtime. The SSR full-text estimate still paints first, so a doc whose
overlay never runs is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sjmiller609
sjmiller609 force-pushed the hypeship/viewer-read-time branch from b5e5311 to d46dc4a Compare July 31, 2026 15:41
@sjmiller609 sjmiller609 changed the title Hypeship/viewer read time Color the read-time chip and measure only visible text Jul 31, 2026
@sjmiller609
sjmiller609 merged commit 89258d2 into main Jul 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant