feat(pages): split comment mode from the comments panel, theme published pages
Summary
- Comments: the comment button now only enters comment mode; a floating "All comments" button opens the thread list, which stays open until you close it.
- Theme: every published page is served a default stylesheet built from Superset's own tokens, injected at view time, so pages look like they belong to the product.
- Marketing (two unrelated fixes, called out below): the contribution graph opens on the latest week, and the leaderboard profile OG image no longer 500s.
Why / Context
The comment button did two jobs at once — arming the page for pin placement and opening the list. On web the list was a permanent 300px rail; on desktop it appeared and vanished with comment mode, so reading a thread meant arming the page for a stray click. Published pages, meanwhile, were whatever HTML an agent wrote against browser defaults, so two pages from the same workspace looked like different products.
How It Works
Comments panel. enabled (pin placement) and panelOpen (the list) are now independent state on CommentProvider; leaving comment mode no longer closes the panel, and closing the panel clears the thread selection so the on-page popover cannot inherit it. CommentsSidebar became CommentsPanel: a floating card over the top-right of the page from md up, a sheet over the pane below it (absolute, not fixed — a desktop window can be narrower than md). Threads group by day and each is a card with its own reply box; past three comments the middle folds behind a count. While the panel is open it is the only reading surface — a pin click selects the thread there rather than also drawing a popover.
Page theme. apps/usercontent already rewrites each document on the way out to inject the comment runtime; it now also inlines a <style> at the top of <head>, which reaches pages published before it existed. Every rule is wrapped in :where() (zero specificity), so any selector a page writes wins outright — that is what makes it safe to apply retroactively. Palette is the app's own oklch values from packages/ui/src/globals.css. The theme is light unless a page opts in: dark or light on <body> pins it, auto follows prefers-color-scheme. It is not automatic, because a page that declared a background but no text colour would otherwise take the two halves from different palettes and end up unreadable. The page skill documents the structure, tokens and theming.
OG image. The profile OG route loaded its font with fetch(new URL(…, import.meta.url)) — the edge-runtime pattern — but declares no runtime, so it runs on Node where fetch cannot read file://. Every share of a leaderboard profile had no image. Now read off disk with fs from public/, matching the blog OG route.
Manual QA Checklist
Comments (web + desktop)
- Comment button toggles comment mode only; no list opens
- "All comments" opens the panel; panel stays open after turning comment mode off
- Closing the panel does not pop a thread open on the page
- Clicking a pin highlights the thread in the panel and scrolls it into view
- Focusing a card's reply box selects that thread (and does not deselect on refocus)
- Thread with 5+ comments folds to first + "Show N replies" + last two
- Below
md: panel is a sheet over the pane only, and the pill is visible without comment mode - Narrow desktop window (<768px): panel does not cover the sidebar or other panes
Page theme
- A newly published page picks up the theme without its own
<style> -
<body class="dark">andclass="light"flip the whole page,class="auto"follows the system - A page that sets its own
bodybackground still wins - An existing page published before this change still renders sanely
Marketing
- Contribution graph opens scrolled to the most recent week
-
/user/<handle>/opengraph-imagereturns a PNG, not a 500
Testing
bun run typecheck(ui, web, desktop, shared, usercontent, marketing)bun run lintbun test—packages/uiPageComments (39) andpackages/sharedusercontent (39, incl. newinject.test.ts)bun run check:i18n— new strings translated into all 16 catalogsbun run check:plugins- OG route verified end-to-end against a dev server from this checkout: 200, 1200×630 PNG
Design Decisions
:where()over a scoped class for the theme: pages are authored by agents as free-form HTML with no wrapper we control, so specificity zero is the only way to apply a default that a page can always override.- No
displayon the theme'stablerule::where()neutralizes specificity, not the cascade for properties a page never declares.display: blockwould silently re-lay-out existing tables that set onlywidth. - Panel entry point is the pill, not a header button: one affordance at every width, and below
mdit stays visible so reading never arms the page.
Known Limitations
- The theme still reaches already-published pages for properties they never declared (
box-sizing, typographic margins), which is the point of a shared default. The rules that actually reshaped a page are gone: nobodypadding, no measure cap, noheighton iframes, and no palette that inverts under a page that never asked. Measured against the four agent-authored documents in this repo, all four now render identical contrast, padding and text width with the theme applied; one is 8% taller from margins alone. - Not verified in a running app for the comments panel: the desktop Electron instance exposes no CDP port and the web viewer needs a signed-in session.
plugins/superset/skills/page/SKILL.mdchanged but the plugin is not bumped or tagged here, so hosts keep the old copy until it is published.check:pluginspasses.
Notes for reviewers
The marketing commits are unrelated to the pages work and are split out — 8be6d02 (contribution graph) and 254b7b3 (OG font) can be reviewed on their own.
Summary by cubic
Separates comment mode from the thread list: the comment button now only arms the page for pin placement, and a floating "All comments" button opens the panel, which stays open until you close it. Published pages now receive an inlined default theme built from Superset's tokens, with dark mode opt-in via body.dark or body.auto so existing pages aren't reshaped. All comment mutations — create, reply, edit, resolve, delete — send optimistically: the box clears immediately, the change appears at once, and failure rolls back and restores the text for retry.
Bug Fixes
- Contribution graph opens scrolled to the most recent week.
- Leaderboard profile OG image no longer 500s; the font is read from disk.
- Theme injection finds the real
<head>tag, skipping comments, raw-text elements, and>inside quoted attributes. - Mutations share one in-flight counter, so editing, resolving, or deleting while a comment is still sending no longer drops the optimistic row.
- Optimistic rows block edit, resolve, and delete until the server has seen their id, so those ids never reach the API.
- A failed edit keeps the editor open with the typed text instead of closing as if the save had landed.
- The theme no longer reshapes pages published before it: dark mode is opt-in, and background/colour live on
htmlso a page's own declarations win.
Migration
- The theme applies retroactively; page-authored selectors always win because rules use zero-specificity
:where(). Dark mode only applies when a page setsbody.darkorbody.auto.
Summary by CodeRabbit
- New Features
- Redesigned page comments with an all-comments button and panel.
- View, reply to, edit, resolve, and delete threads with grouped activity and expandable replies.
- Comment creation and replies appear immediately, with recovery after failed submissions.
- Added light and dark theme styling to served pages.
- Contribution graphs open at the most recent activity.
- Accessibility
- Improved keyboard focus when opening and closing comment panels.
- Localization
- Added translations for comment controls, loading and empty states, resolved comments, version notices, and unpinned comments.
- Documentation
- Updated guidance for themes, design tokens, and dark mode.