Skip to content

Put every page on one shared shell and serve them all - #3

Merged
vroslmend merged 3 commits into
mainfrom
reader-ui-overhaul
Aug 8, 2026
Merged

vroslmend merged 3 commits into
mainfrom
reader-ui-overhaul

Conversation

@vroslmend

Copy link
Copy Markdown
Owner

The reader and the generated pages had grown apart. Presentation was defined three separate times, the server routed a fraction of what the analyzer wrote, and most of the report's own navigation answered 404 while being served.

What was broken

Probing the routes against a generated sample run:

200  /t/saturday_squad/report.html
200  /t/saturday_squad/year_in_review.html
404  /t/saturday_squad/eras.html
404  /t/saturday_squad/sessions.html
404  /t/saturday_squad/quiz.html
404  /t/saturday_squad/relationships.html
404  /t/saturday_squad/group_history.html
404  /t/saturday_squad/trendsetters.html
404  /t/saturday_squad/member_alice.html
404  /t/saturday_squad/year_2017.html   <- what the year index links to

Alongside that: five different page headers, two theme mechanisms that disagreed about the default and one that persisted nothing, and a reader whose sticky day headers hid behind its own toolbar at any width where the toolbar wrapped (top: 52px hardcoded against a bar measuring 153px on a phone).

What this does

chatpage.py owns the tokens, the shell, the single navigation builder and the theme. Both the analyzer and the reader import it, so the two families cannot fall out of step. Navigation is built from the set of pages a run actually wrote — the old code filtered sibling links on the report and not on the narrative pages, so they disagreed about what existed.

  • Routing. One validated static rule serves every generated page; /year/<n> stays as an alias.
  • Theme. One key, three states: dark, light, or unset and following the system. Applied in <head>, so no flash, and it survives navigation.
  • Reader. Sticky offset measured from the bars rather than assumed. Two-row toolbar that collapses behind a More disclosure. Consecutive-sender grouping, with the date off timestamps the day header already carries. Sentiment as an edge marker rather than a full-row tint that read as hover. Full URL state, so back and forward work and a view can be linked to.
  • Cross-links. Quoted messages carry their timestamp and become jump links into the reader — but only while one is running. Read the same file off disk and they stay plain text rather than becoming dead links.
  • Hierarchy. A members index (member pages were reachable only by noticing that a leaderboard name happened to be a link), prev/next between members and between years, and a section sidebar that tracks where you are on long pages.
  • Quiz. Reveals who actually said it and waits, instead of flashing the answer for 650ms and advancing.

Bugs found on the way

  • The lazy-load observer raced the initial page load and appended history twice — a 94-message chat reported "188 of 94 messages loaded". Predates this branch.
  • Windows honours SO_REUSEADDR by letting a second reader bind a port already in use, leaving two of them answering at random and one serving whatever the code said when it started. Now refused with a message.
  • Sticky <th> landed on top of the first row once tables gained a horizontal-scroll wrapper.
  • Duplicate id="quiz" on the section and its container made the quiz render over its own heading.

Testing

284 pass, up from 275. The nine new ones generate a real report, serve it, and walk it — every href has to answer 200, the nav has to be identical everywhere, the current page has to be marked rather than dropped, and the reader hook has to stay a comment on disk and become a script when served. That is the test that would have caught all seven dead links.

One existing test needed a call-signature update; no behaviour changed under it.

The committed examples/ and both README screenshots are regenerated, since they still showed the old per-page headers.

The tool defined its presentation three separate times: one stylesheet for
the year and narrative pages, a second inside the report, and a third inside
the reader. They drifted, and the navigation drifted with them.

The server routed three of the eight pages the analyzer writes, so seven of
the eight links in the report's own header answered 404 while being served,
along with the year index's links and every member page.

chatpage.py now owns the tokens, the shell, the one navigation builder and
the theme, and both the analyzer and the reader import it. Navigation is
built from the set of pages a run actually wrote, so a link cannot point at
a page that was skipped, and the current page is marked rather than dropped.

Also here:

- One validated static route serves every generated page, with /year/<n>
  kept as an alias.
- Theme is one key with three states: dark, light, or unset and following
  the system. Applied in <head>, so a stored choice no longer flashes the
  other theme first, and it survives navigation.
- The reader measures the bars above it instead of assuming 52px, which put
  every day header underneath the toolbar the moment it wrapped.
- Reader state lives in the address bar, so back and forward work and a view
  can be linked to. Quoted messages on the narrative pages and answers in
  the quiz link into it, but only while a reader is running.
- Messages from one sender in a burst group under a single avatar, and the
  date comes off timestamps the day header already carries.
- A members index, since member pages were reachable only by noticing that a
  name in the leaderboard happened to be a link. Prev/next between members
  and between years.
- The quiz reveals who actually said it and waits, instead of flashing an
  answer for 650ms and moving on.
- Report cards sit on a grid instead of running ragged, and the section list
  moved to a sidebar that tracks where you are.

Two bugs found on the way: the lazy-load observer raced the initial page and
appended history twice, which is how a 94-message chat reported "188 of 94
messages loaded"; and Windows honoured SO_REUSEADDR by letting a second
reader bind a port already in use, leaving two of them answering at random.
Nothing caught the dead navigation because no test ever followed a link.
These generate a real report, serve it, and walk it: every href on every
page has to answer 200, the nav has to be the same set everywhere, the
current page has to be marked rather than dropped, and the member and year
pages have to be reachable from an index.

Also covers the reader hook, which has to stay a comment on disk and become
a script when served, and the timestamps the jump links are built from.
The committed sample run and its two screenshots still showed the old
per-page headers. README gains the members index, the shared navigation and
the deep links.
@vroslmend
vroslmend merged commit 75843d6 into main Aug 8, 2026
2 checks passed
@vroslmend
vroslmend deleted the reader-ui-overhaul branch August 8, 2026 21:46
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