| commit | e68613d5105c86652d5287102e1bd4ad0859b781 | [log] [tgz] |
|---|---|---|
| author | Sean McCullough <banksean@gmail.com> | Wed Jun 18 14:48:53 2025 +0000 |
| committer | Autoformatter <bot@sketch.dev> | Wed Jun 18 23:44:00 2025 +0000 |
| tree | ed2b409f2c3649ae0afc14cc3df31c89382106eb | |
| parent | e0a9f7252a39c66ffa409daa9fd0ac5c6b08a100 [diff] |
webui: implement viewport-based message rendering in sketch-timeline
Add viewport-based rendering to SketchTimeline component to optimize performance
with large conversation histories by only rendering messages in current viewport.
Implementation Changes:
1. Viewport Management:
- Add initialMessageCount property (default: 30) to control initial render
- Add loadChunkSize property (default: 20) for batch loading older messages
- Add visibleMessageStartIndex state to track current viewport window
- Add isLoadingOlderMessages state to prevent concurrent load operations
2. Message Filtering and Windowing:
- Create filteredMessages getter to exclude hidden messages
- Create visibleMessages getter to return current viewport slice
- Implement loadOlderMessages() to expand viewport window on scroll
- Preserve scroll position when prepending older messages
3. Scroll-Based Loading:
- Add loadMoreThreshold property (100px from top) for trigger distance
- Enhance _handleScroll() to detect near-top scroll and trigger loading
- Add loading indicator with spinner for older message loading states
- Maintain existing 'pinToLatest' and 'floating' scroll behaviors
4. Updated Rendering Logic:
- Replace direct messages.filter() with visibleMessages getter
- Add loading indicator rendering above message list
- Preserve message key generation for efficient re-rendering
- Maintain proper previousMessage calculation for filtered messages
5. Lifecycle Management:
- Reset viewport window on significant message changes
- Preserve scroll-to-bottom behavior for new messages
- Handle edge cases for empty messages and initial load states
Technical Details:
- Uses slice-based windowing instead of full virtual scrolling for simplicity
- Implements scroll position preservation using scrollHeight differences
- Maintains efficient message key generation for Lit's repeat directive
- Preserves all existing timeline functionality and styling
- Loading indicator appears only during older message fetching operations
Benefits:
- Significant performance improvement for large conversation histories
- Reduced initial render time by limiting message count
- Progressive loading maintains responsive UI during scroll
- Maintains existing scroll behaviors and user experience
- Memory usage scales with viewport size rather than total messages
This implements the requested viewport-based rendering while preserving all
existing SketchTimeline functionality and user experience patterns.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sbe64498bdb5fd1cck
webui: fix viewport rendering to always show most recent messages initially
Fix viewport calculation logic in SketchTimeline to ensure the most recent
messages are always displayed on initial load, addressing issue where first
messages were shown instead of latest ones.
Root Cause:
The original viewport calculation used subtraction logic that was prone to
showing older messages when the viewport state wasn't properly initialized
or when messages loaded incrementally.
Implementation Changes:
1. Simplified Viewport Logic:
- Replace complex subtraction-based calculation with direct slice approach
- Use 'totalVisible = initialMessageCount + visibleMessageStartIndex'
- Calculate startIndex as 'max(0, filteredMessages.length - totalVisible)'
- Always slice from startIndex to end to show most recent messages
2. Enhanced Viewport Reset Logic:
- Trigger viewport reset for large message count changes (>20 difference)
- Reset viewport on initial load (oldMessages.length === 0)
- Reset viewport when message count decreases (session change)
3. Added Public Reset Method:
- Add resetViewport() public method for external viewport reset
- Useful for app shell to call when loading new sessions
- Updated demo to demonstrate manual viewport reset
4. Improved Demo:
- Add expected message range display in demo info
- Add viewport reset button for testing
- Call resetViewport() on message generation for consistent behavior
Technical Details:
- Viewport now correctly shows messages [N-30, N] initially for N total messages
- Scroll-up loading expands to show [N-30-X, N] where X is loaded chunk size
- Eliminates race conditions between message loading and viewport calculation
- Maintains all existing scroll behaviors and performance optimizations
This ensures users always see the most recent conversation content when
loading sessions with large message histories, matching expected chat UX.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s56dda43278ce4d5bk
webui: fix scroll container height and demo setup for viewport rendering
Fix scroll container CSS and demo JavaScript to enable proper scrolling and
viewport testing in the sketch-timeline component.
CSS Fix:
- Add 'height: 100%' to #scroll-container to properly constrain container height
- Enables overflow scrolling when content exceeds available space
- Fixes issue where scrollHeight === clientHeight prevented scrolling
Demo Setup Fix:
- Correct scrollContainer property setup using shadow DOM reference
- Wait for component render before setting scroll container reference
- Use proper Lit Ref pattern: { value: scrollContainerElement }
- Add console logging for debugging scroll container setup
Testing Results:
- Viewport rendering now works correctly with 500+ messages
- Initial load shows most recent 20 messages (e.g., 481-500 for 500 total)
- Scroll-up loading successfully expands viewport (20 → 30 → 40 messages)
- Proper scroll position preservation when loading older messages
- Jump-to-latest button appears when not pinned to bottom
This fixes the demo functionality and confirms viewport rendering works as
designed for performance optimization with large conversation histories.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s998bb29cf9f06291k
webui: eliminate setTimeout in viewport demo for reliable initialization
Replace setTimeout-based initialization with proper event-driven setup using
MutationObserver and Lit's updateComplete promise for robust demo functionality.
Problems with setTimeout Approach:
- Race conditions between component rendering and scroll setup
- Arbitrary delays cause flakiness in different environments
- No guarantee that shadow DOM or scroll container exists after timeout
- Unreliable for automated testing or slower systems
Improved Event-Driven Approach:
1. MutationObserver Pattern:
- Watch for shadow DOM creation using MutationObserver
- Disconnect observer once shadow DOM is detected
- Eliminates timing-based guesswork
2. Lit updateComplete Promise:
- Use timeline.updateComplete to ensure full component render
- Promise-based approach guarantees completion before setup
- Handles both initial render and re-renders reliably
3. Robust Fallback Strategy:
- Try immediate setup first (component may already be ready)
- Use MutationObserver for shadow DOM detection
- Apply updateComplete promise for render completion
- Multiple strategies ensure setup works in all scenarios
4. Promise-Based Message Generation:
- Use updateComplete in generateMessages() for reliable info updates
- Ensure scroll container setup after each message array change
- Eliminates race between message updates and UI state
Testing Results:
- Reliable setup across page reloads and component re-initialization
- Consistent scroll container configuration without timing issues
- Proper viewport loading functionality (20→30 messages on scroll up)
- No console errors or failed setup attempts
This eliminates demo flakiness and provides a robust example of proper
Lit component initialization patterns for complex shadow DOM interactions.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s6b897544f3af8454k
webui: fix memory leaks and race conditions in sketch-timeline scroll handling
Implement proper event listener cleanup and debounced scroll handling to prevent
memory leaks and race conditions in SketchTimeline incremental rendering.
Memory Leak Fixes:
1. Scroll Container Tracking:
- Add currentScrollContainer property to track active scroll listener
- Implement addScrollListener() with automatic cleanup of previous listeners
- Implement removeScrollListener() with guaranteed cleanup
- Replace fragile scrollContainer.value?.removeEventListener with tracked cleanup
2. Event Listener Lifecycle:
- Ensure proper cleanup in disconnectedCallback() using removeScrollListener()
- Handle scrollContainer property changes with proper cleanup sequence
- Add scroll timeout cleanup to prevent lingering timers
- Track and clean up scroll container references to prevent stale listeners
Race Condition Prevention:
3. Debounced Scroll Handling:
- Add scrollTimeout property to debounce scroll events
- Implement 100ms debounce for loadOlderMessages() calls
- Maintain immediate scroll state updates for responsive UI
- Clear pending timeouts during cleanup to prevent memory leaks
4. Loading State Protection:
- Add comprehensive error handling in loadOlderMessages()
- Implement 5-second timeout fallback to prevent stuck loading state
- Add bounds checking for visibleMessageStartIndex calculation
- Use try-catch blocks for scroll position restoration
5. Robust Error Recovery:
- Check container.isConnected before DOM manipulation
- Add fallback timeout to reset loading state if updateComplete fails
- Log warnings for debugging without breaking functionality
- Ensure isLoadingOlderMessages always gets reset
Technical Implementation:
- Uses proper TypeScript typing with HTMLElement | null for container tracking
- Implements window.setTimeout for proper timeout management
- Maintains all existing scroll behavior while preventing memory leaks
- Preserves scroll position restoration with error recovery
- Compatible with existing viewport rendering functionality
Testing:
- Add testMemoryLeakFix() function to viewport demo for validation
- Verify cleanup happens correctly during scroll container changes
- Confirm no lingering event listeners after component disconnection
- Test loading state timeout recovery mechanisms
This resolves the critical memory leak issues identified in the timeline
component while maintaining all existing functionality and user experience.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: seb56a936de452cefk
webui: enhance memory leak test in timeline viewport demo
Add comprehensive test for scroll event listener cleanup validation with
improved logging and multiple cleanup scenarios for testing memory leak fixes.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sd1928398dca67ad9k
webui: eliminate race conditions in sketch-timeline scroll handling and loading operations
Implement comprehensive race condition prevention in SketchTimeline incremental
rendering with async loading operations and proper state management.
Race Condition Fixes:
1. Async Loading Operations:
- Convert loadOlderMessages() to async/await pattern for proper sequencing
- Add currentLoadingOperation tracking to prevent concurrent loads
- Implement executeScrollPositionRestoration() with proper error handling
- Use Promise-based DOM update waiting instead of fire-and-forget callbacks
2. Loading State Management:
- Add cancelCurrentLoadingOperation() method for safe operation cancellation
- Implement clearAllPendingOperations() to clean up all timeouts and operations
- Add loadingTimeoutId tracking for proper timeout cleanup
- Add pendingScrollRestoration tracking for cancellable scroll operations
3. Scroll Container Validation:
- Add isStableForLoading() method to validate component state before operations
- Verify scroll container hasn't changed during async operations
- Check container.isConnected before DOM manipulation
- Validate container matches currentScrollContainer throughout operation lifecycle
4. Property Change Coordination:
- Cancel loading operations when scrollContainer property changes
- Cancel operations during significant message array changes
- Handle viewport resets during loading with proper state cleanup
- Prevent scroll-to-bottom during loading operations to avoid conflicts
5. Enhanced Scroll Position Restoration:
- Add comprehensive validation for scroll calculations before applying
- Implement bounds checking for scroll position values
- Add debug logging for invalid restoration attempts
- Ensure restoration only happens if calculations are mathematically valid
6. Component Lifecycle Protection:
- Cancel loading operations in disconnectedCallback() before cleanup
- Handle rapid property changes without state corruption
- Prevent operations on disconnected or invalid containers
- Ensure all timeouts and promises are cleaned up during disconnection
Technical Implementation:
- Uses async/await throughout loading pipeline for proper sequencing
- Implements operation cancellation with proper cleanup guarantees
- Added container stability checks before all DOM operations
- Uses typed Promise returns for better error handling
- Maintains backward compatibility with existing scroll behavior
Testing Enhancements:
- Add testRaceConditions() function to demo for validation
- Test rapid viewport resets, container changes, and message updates
- Verify graceful handling of component state changes during loading
- Validate proper cleanup during simulated disconnection scenarios
Benefits:
- Eliminates concurrent loading operations that could corrupt state
- Prevents scroll position restoration conflicts and invalid calculations
- Ensures consistent component state during rapid user interactions
- Provides robust error recovery for all async operations
- Maintains responsive UI while preventing race-related bugs
This resolves all identified race conditions while preserving existing
functionality and improving overall component reliability and performance.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sc0c567cfff13ae3fk
webui: replace setTimeout with event-driven patterns in sketch-timeline
Eliminate setTimeout dependencies in favor of proper event-driven and async/await
patterns for more reliable and performant timeline operations.
Event-Driven Replacements:
1. Scroll Debouncing:
- Replace setTimeout-based debouncing with requestAnimationFrame
- Use scrollDebounceFrame with cancelAnimationFrame for smooth performance
- Eliminate arbitrary 100ms delays in favor of browser-optimized frame timing
- Maintain responsive UI updates while preventing excessive loading calls
2. Loading Operation Management:
- Replace setTimeout fallback with AbortController for proper cancellation
- Add loadingAbortController for clean operation abortion
- Implement signal-based cancellation throughout loading pipeline
- Remove 5-second timeout fallback in favor of proper Promise rejection handling
3. Scroll Position Restoration:
- Replace setTimeout retry logic with ResizeObserver-based content detection
- Add waitForContentReady() using ResizeObserver to detect when DOM is ready
- Use requestAnimationFrame for frame-perfect scroll position updates
- Eliminate arbitrary delays and retry intervals
4. Auto-Scroll to Bottom:
- Replace setTimeout-based retry with MutationObserver approach
- Use scrollToBottomWithRetry() with event-driven content change detection
- Implement requestAnimationFrame for smooth scroll operations
- Remove hardcoded retry intervals and attempt limits
5. Component Lifecycle:
- Add disconnectObservers() for proper cleanup of ResizeObserver and MutationObserver
- Replace clearTimeout calls with cancelAnimationFrame and AbortController.abort()
- Ensure all async operations can be properly cancelled during component lifecycle
Technical Benefits:
- Uses browser-native APIs (ResizeObserver, MutationObserver, AbortController)
- Eliminates race conditions from setTimeout timing assumptions
- Provides frame-perfect animations with requestAnimationFrame
- Enables proper cancellation of async operations with AbortController
- Reduces arbitrary delays and improves perceived performance
Implementation Details:
- AbortController provides clean cancellation semantics for loading operations
- ResizeObserver detects content changes without polling or timeouts
- MutationObserver monitors DOM changes for scroll position adjustments
- requestAnimationFrame ensures operations happen at optimal frame timing
- All observers are created on-demand and properly cleaned up
Testing Enhancements:
- Add testEventDriven() function to validate no setTimeout usage
- Test AbortController availability and proper operation cancellation
- Verify ResizeObserver, MutationObserver, and requestAnimationFrame support
- Monitor setTimeout calls during operations to ensure elimination
This modernizes the timeline component to use proper browser APIs instead of
setTimeout workarounds, improving reliability and performance while eliminating
timing-based race conditions.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: se869d73b455454a5k
Sketch is an agentic coding tool. It draws the 🦉
Sketch runs in your terminal, has a web UI, understands your code, and helps you get work done. To keep your environment pristine, sketch starts a docker container and outputs its work onto a branch in your host git repository.
Sketch helps with most programming environments, but Sketch has extra goodies for Go.
go install sketch.dev/cmd/sketch@latest sketch
Currently, Sketch runs on macOS and Linux. It uses Docker for containers.
| Platform | Installation |
|---|---|
| macOS | brew install colima (or Docker Desktop/Orbstack) |
| Linux | apt install docker.io (or equivalent for your distro) |
| WSL2 | Install Docker Desktop for Windows (docker entirely inside WSL2 is tricky) |
The sketch.dev service is used to provide access to an LLM service and give you a way to access the web UI from anywhere.
Start Sketch by running sketch in a Git repository. It will open your browser to the Sketch chat interface, but you can also use the CLI interface. Use -open=false if you want to use just the CLI interface.
Ask Sketch about your codebase or ask it to implement a feature. It may take a little while for Sketch to do its work, so hit the bell (🔔) icon to enable browser notifications. We won't spam you or anything; it will notify you when the Sketch agent's turn is done, and there's something to look at.
When you start Sketch, it:
This design lets you run multiple sketches in parallel since they each have their own sandbox. It also lets Sketch work without worry: it can trash its own container, but it can't trash your machine.
Sketch's agentic loop uses tool calls (mostly shell commands, but also a handful of other important tools) to allow the LLM to interact with your codebase.
Sketch is trained to make Git commits. When those happen, they are automatically pushed to the git repository where you started sketch with branch names sketch/*.
Finding Sketch branches:
git branch -a --sort=creatordate | grep sketch/ | tail
The UI keeps track of the latest branch it pushed and displays it prominently. You can use standard Git workflows to pull those branches into your workspace:
git cherry-pick $(git merge-base origin/main sketch/foo)
or merge the branch
git merge sketch/foo
or reset to the branch
git reset --hard sketch/foo
Ie use the same workflows you would if you were pulling in a friend's Pull Request.
Advanced: You can ask Sketch to git fetch sketch-host and rebase onto another commit. This will also fetch where you started Sketch, and we do a bit of "git fetch refspec configuration" to make origin/main work as a git reference.
Don't be afraid of asking Sketch to help you rebase, merge/squash commits, rewrite commit messages, and so forth; it's good at it!
The diff view shows you changes since Sketch started. Leaving comments on lines adds them to the chat box, and, when you hit Send (at the bottom of the page), Sketch goes to work addressing your comments.
You can interact directly with the container in three ways:
ssh sketch-ilik-eske-tcha-lott. We have automatically configured your SSH configuration to make these special hostnames work.Using SSH (and/or VSCode) allows you to forward ports from the container to your machine. For example, if you want to start your development webserver, you can do something like this:
# Forward container port 8888 to local port 8000 ssh -L8000:localhost:8888 sketch-ilik-epor-tfor-ward go run ./cmd/server
This makes http://localhost:8000/ on your machine point to localhost:8888 inside the container.
You can ask Sketch to browse a web page and take screenshots. There are tools both for taking screenshots and "reading images", the latter of which sends the image to the LLM. This functionality is handy if you're working on a web page and want to see what the in-progress change looks like.
Docker images, containers, and so forth tend to pile up. Ask Docker to prune unused images and containers:
docker system prune -a
See CONTRIBUTING.md for development guidelines.
Sketch is open source. It is right here in this repository! Have a look around and mod away.
If you want to run Sketch entirely without the sketch.dev service, you can set the flag -skaband-addr="" and then provide an ANTHROPIC_API_KEY environment variable. (More LLM services coming soon!)