feat: collaborative Wikipedia browsing — link glow, cursor following, and tooling#80
Open
spencerc99 wants to merge 60 commits intomainfrom
Open
feat: collaborative Wikipedia browsing — link glow, cursor following, and tooling#80spencerc99 wants to merge 60 commits intomainfrom
spencerc99 wants to merge 60 commits intomainfrom
Conversation
…ether, and navigation
…ty scaling - Logarithmic + absolute dampener intensity scaling (ABSOLUTE_RATE=50) so single clicks barely show and glow grows naturally with traffic - Single-line links: pseudo-element blur via injected stylesheet - Multi-line links: inline backgrounds with box-decoration-break + drop-shadow - Inline opacity reduced to 40% to compensate for no blur - Decay mechanism (subtract 5 every 100 total clicks) prevents unbounded growth - Preview page: early-visit test cases, hover tooltips, click-to-copy data - Remove debug console.log statements, fix GlowStyle import
- LinkGlowManager: replace hidden <div can-play> hack with playhtml.createPageData() for persistent shared link click data - FollowManager: replace broken window.cursors callbacks with playhtml.presence API for reading/writing ephemeral state - Remove manual proximity detection, use cursor client's built-in onProximityEntered/onProximityLeft callbacks instead - Broadcast navigatingTo/following via presence.setMyPresence()
- Create extension/src/custom-sites/ with domain dispatcher pattern - wikipedia.ts: link glow init + delayed navigatingTo broadcast - content.ts: delegate to initCustomSite() instead of inline Wikipedia code - Move isWikiArticleUrl from FollowManager to wikipedia module
- Track last seen navigatingTo so follower sees nav toast even if target disconnects before the poll catches it - Clear stale navigatingTo when restoring follow from sessionStorage - Poll at 150ms instead of 200ms for better responsiveness
- OffscreenIndicator: circular pip at viewport edge showing direction to cursors above/below the viewport, colored by cursor identity - FollowManager: detect when others are following you, show subtle top bar with follower color dots and count - Scroll tether tick updates off-screen indicator for followed cursor - Follower watcher updates indicators for followers' cursors
Follow hint, scroll tether, navigation following, and off-screen indicators are now Wikipedia-only. Proximity callbacks are wired via cursorClient.configure() in wikipedia.ts instead of at init.
- PresenceCountPill now shows "N here · M elsewhere [portal]"
- Portal button jumps to a random page where someone else is browsing
- Domain-wide lobby created via createPresenceRoom("lobby")
- Each user broadcasts their current page URL/title into the lobby
- Depends on playhtml.createPresenceRoom (pending core PR)
- Fix nav watch interval leak: track separately, clear on unfollow - Only delay navigation when user has followers (solo clicks are instant) - Guard lobby creation with runtime check for missing createPresenceRoom - Track leftToast timeout, clear on unfollow to prevent double-fire - Remove dead navToastTimeout field
… test - Hint now positioned near the other cursor and updates as they move - Multiple nearby cursors: only shows hint for the closest one - nearbyCursors map tracks all nearby cursors with distances - lobby-presence load test scenario: 10k users in a domain-wide lobby each broadcasting a random Wikipedia page URL via awareness
- Extract applyGlowToLink() and buildGlowCssRules() as standalone exported functions in link-glow-renderer.ts - LinkGlowManager delegates to shared functions instead of inline logic - Preview SmearLink uses the same shared renderer via cross-import - Preview linkIntensity() delegates to shared computeIntensity() - Single source of truth for glow visual logic
…move debug logs - Apply OPACITY_MUL=0.6 to pseudo-element blur path (matching preview tuning) - Inline path now uses 0.6 * 0.4 = 0.24 effective opacity - Add z-index: 1 to <a> so text renders above pseudo-element glows - Save/restore position and z-index in original styles - Remove all debug console.log statements
- Extract applyInlineGlow, applySingleLineGlow, buildPseudoElementCSS as standalone exported functions in link-glow-renderer.ts - LinkGlowManager delegates to shared functions instead of inline logic - Preview's SmearLink imports from extension renderer via cross-import - Remove duplicate averageHex, pairColors, smearNebulaLayers, buildGlowShadows, ltHexToRgba from preview - Remove opacity/saturation tuning sliders (now baked into renderer) - Single source of truth for all glow rendering math and DOM application
…st click - Add @extension alias in vite.config.site.mts for website to import from extension/src/ - Lower base opacity constants so count=1/pageMax=1 gives ~0.03 alpha instead of 0.09 (vivid player colors were too prominent at low counts)
data.t is the CSS selector of the input field, not the typed text. domain field is missing from events, must be parsed from meta.url. Also trim unused fonts from conversations.html.
Detects messages where >70% of characters are the same char (e.g. "sssssss", "rrrrrr") and excludes them.
Add white-space: pre-wrap so \n characters from Enter keys render as actual line breaks in message bubbles.
The old approach diffed textContent before/after each input event, assuming new text was always appended at the end. This caused: 1. Repeated character spam on contenteditable (Gmail, Instagram DMs) because editor DOM reflows triggered phantom text changes via textContent reading internal/duplicate DOM nodes. 2. Corrupted text when typing in the middle of existing text, since textAfter.slice(textBefore.length) captures the wrong characters. Now uses InputEvent.inputType and InputEvent.data which the browser provides directly, eliminating the need for text diffing entirely. Also switches from textContent to innerText for contenteditable elements, which respects rendered content rather than raw DOM nodes.
Toggle with double-tap 'd'. Shows: - Start time datetime picker with clear button - Message count (filtered / total) - Restart animation button - Domain histogram with favicons and bar chart Also fixes default start time to show all data instead of filtering to a hardcoded date.
Config panel now renders as a sticky sidebar on the left instead of inline in the conversation. Subtitle always shows "starting from" with either the filtered start time or the earliest message date.
After all messages have been shown, pauses 3 seconds then resets and replays from the beginning. Scrolls back to top on each loop iteration.
Client fetches keyboard events in pages of 5000 using the 'to' date param. When the animation exhausts the current batch, it requests the next older page. When all data is exhausted, loops back to the beginning after a 3-second pause. Animation state is preserved across page fetches so it resumes from where it left off rather than restarting.
Replaces datetime-local input with a select dropdown showing all available dates with message counts (e.g. "2026-01-25 (42)"). "all dates" option shows total count. Selecting a date sets the start time filter.
Click any domain in the histogram to exclude/include it. Excluded domains are dimmed and their messages are filtered out. Setting persists in localStorage across sessions.
- Day/time sort mode toggle: "by day" (chronological) vs "by time of day" (merges messages across days, sorted by time-of-day) - Max consecutive per domain slider (0-20, default 4): caps runs of same-domain messages for more varied conversation flow - Domain search bar: filter the domain list to find/toggle any domain - Domain list now shows all domains (scrollable) instead of top 15 - All settings persist in localStorage
Time mode no longer shows date headers since messages are merged across days. A time slider (00:00-23:30, 30min steps) appears in the config panel when time mode is active, letting you set what time of day to start the conversation from. Persists in localStorage.
Speed slider in config panel scales all animation timings: typing indicator duration, character type-in rate, and inter-message pauses. Uses a ref so speed changes take effect immediately without restarting the animation. Persists in localStorage.
Use scrollIntoView on the last stream element so the whole page scrolls (not just the stream div). Also trigger scroll on typing text changes so it keeps up during character-by-character animation. Speed slider max reduced from 20x to 10x.
Auto-scroll locks on when at the bottom of the page, unlocks when you scroll up. A floating "follow" button appears in the bottom-right when unlocked, clicking it re-locks and scrolls to the latest message.
Unlocks only when user scrolls up by >10px (not on every scroll). Re-locks when user scrolls to near bottom. Programmatic scrolls from scrollIntoView are ignored via a ref flag with 500ms cooldown.
…onUpdate The onUpdate callback fires via queueMicrotask which may be too late if the page is navigating away. Read data synchronously after setData and call renderGlows immediately.
… bounds The proximity callback provides document (page) coordinates, but the hint uses position: fixed which needs viewport coordinates. Subtract scroll position and clamp to viewport edges.
- Move load-test/ to tools/load-test/ - Add tools/playwright/ for browser choreography and demo recording - Scene definition format with defineScene() - Runner with extension loading, setup flow completion, video recording - smoothMove with eased cubic + organic wobble - wiki-follow-demo scene: two cursors meet, follow, navigate together - Add tools/playwright/videos and .superpowers to gitignore
- Remove 5 debug console.log statements from production code - Add ABOUTME header to content.ts - Fix hint tracking to use presence API instead of fragile DOM query (correctly matches target by publicKey, works with 3+ cursors) - Rename onProximityLeft param from connectionId to publicKey for clarity - Define WikiPresenceView type, replace all as-any casts on presence data - Add recordActor to SceneConfig interface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds collaborative browsing features to the browser extension for Wikipedia, plus developer tooling for demos and load testing.
Link Glow (Patina)
playhtml.createPageData()box-decoration-break: clonefor multi-line@extensionVite alias)Cursor Following
Presence & Lobby
createPresenceRoom("lobby")Architecture
extension/src/custom-sites/dispatcher pattern for domain-specific featureswikipedia.tsmodule handles all Wikipedia-specific initializationplayhtml.createPageData()andplayhtml.presenceAPIsWikiPresenceViewfor presence data (noas anycasts)Developer Tooling
tools/playwright/— Browser choreography for scripted multi-actor demos with extension loading, setup flow completion, video recording, and organic cursor movementtools/load-test/— Moved from root, addedlobby-presencescenario (tested 500 users successfully)Preview Page
@extensionaliasTest plan
bun tools/playwright/src/runner.ts --scene wiki-follow-demobun tools/load-test/src/runner.ts --scenario lobby-presence --users 500