Enable the mistune table plugin in the markdown renderer to support rendering of markdown tables. Add comprehensive CSS styling for `.message-content` tables including border-collapse, alternating row colors, hover effects, and responsive overflow handling. Style table headers with accent color background and white text, and apply consistent padding and borders to table cells.
The server-side WebSocket close timeout in SocketService was raised from 5s to 300s
to prevent premature timeout during graceful shutdown. The client-side _callTimeout
in socket.js was increased from 30s to 300s to match the longer server timeout,
ensuring pending RPC calls are not cancelled before the connection fully closes.
Handle lines starting with 'export ' in .env files by removing the prefix before splitting key=value, enabling compatibility with shell-exported environment variable formats.
The heartbeat interval is raised from 30.0 to 300.0 seconds to reduce network overhead and improve connection efficiency. Subscription loading is moved into a background async task to prevent blocking the WebSocket handshake, enhancing responsiveness during initial connection setup.
Update pyproject.toml version from 1.34.0 to 1.35.0 and add logic in UserService.create to set is_admin flag when no users exist yet, ensuring the first registered user gains administrative privileges automatically.
Implement full admin interface including dashboard stats, user management (edit/ban), channel management (edit/clear/delete members), message/forum/thread/post browsing, file and drive management, notification creation, system KV store, and push notification views. Add AdminService with permission checks and dashboard statistics aggregation. Include admin.css with dark theme styling and extend base.css for profile page layout fixes. Register all admin routes and service in application bootstrap.
Lower the ThreadPoolExecutor max_workers parameter in ChannelMessageService from 30 to 1 to limit concurrency and improve performance under resource-constrained conditions. Bump project version from 1.30.0 to 1.31.0 and update CHANGELOG.md to document the change.
Bump project version from 1.29.0 to 1.30.0 and update CHANGELOG to document the performance improvement. The change modifies the `_max_workers` attribute in `src/snek/service/channel_message.py` to allow up to 30 concurrent workers for processing channel messages, improving throughput under high load.
Add five new documentation hub pages (architecture, design, api, bots, contribute) with dedicated views and routes in the application router. Increase channel message service max workers from 1 to 10 for improved throughput. Update navigation links in about and index templates to include the new doc pages and the docs hub. Bump project version to 1.28.0.
- Update pyproject.toml version from 1.24.0 to 1.25.0
- Modify sandbox.html StarField instantiation to use 150 stars instead of 50
- Add changelog entry for version 1.25.0 documenting the HTML file update
Rename createChannel, inviteUser, leaveChannel, updateChannel to snake_case equivalents in src/snek/view/rpc.py; update pyproject.toml version from 1.22.0 to 1.23.0; add changelog entry for version 1.23.0
- Update pyproject.toml version from 1.20.0 to 1.21.0
- Change channel name source from subscription["label"] to subscription["description"] in src/snek/view/rpc.py RPCView class
- Update pyproject.toml version from 1.14.0 to 1.15.0
- Add changelog entry for version 1.15.0
- Convert .fug-grid from CSS grid with 6-column fixed layout to flexbox with wrap
- Remove fixed background color on .fug-root and set max-width to 100%
- Adjust tile width to 120px with flex-shrink: 0 and reduce padding/spacing values
Replace direct appending of stt, tts, and upload buttons with a centralized toolbar-menu component. This change moves button management into the new toolbar-menu element, which handles button registration via addButton calls and controls visibility during file upload states. The toolbar-menu is now shown/hidden instead of individual buttons, simplifying the chat input's DOM structure and preparing for future toolbar extensions.
When the user presses Enter while listening, the previous behavior stopped recognition entirely via _stopAndSend(). The new _sendKeepListening() method clears both final and interim transcripts while keeping the recognition session active, allowing continuous dictation across multiple Enter presses without restarting the speech-to-text engine.
- Remove snek-speaker element from chat-input.js and replace with tts-button
- Rewrite tts.js from a simple SnekSpeaker class to a full TTSButton custom element with voice selection, styling, and speech synthesis integration
- Fix goToLine in editor.js to use literal \n instead of broken template literal split
- Fix push.js alert string to include proper newline separator instead of broken concatenation
- Update web.html template to remove snek-speaker speak logic and always play message sound
The previous implementation incorrectly wired a TTS button to trigger speech synthesis instead of speech-to-text. This commit removes the misconfigured `ttsButton` and `snekSpeaker` elements, replacing them with a proper `sttButton` that creates an `STTButton` custom element. The `stt.js` module is fully rewritten to implement a functional Web Speech API-based speech recognition component, including start/stop listening, interim and final transcript handling, and keyboard event simulation for inserting recognized text into the chat input field.
Add an asyncio.Lock to SocketService to synchronize concurrent access to the users and sockets sets during connection and disconnection, preventing race conditions when multiple websocket connections are established or torn down simultaneously. In the frontend MessageList component, replace anonymous inline event listeners with named handler references stored as instance properties, and implement a disconnectedCallback lifecycle method that removes those event listeners and disconnects the MutationObserver, ensuring proper cleanup when the element is removed from the DOM.
Implement five new dialog interfaces (create, settings, delete, invite, leave) with corresponding CSS styling, JavaScript classes, and RPC integration. Add sidebar add-button styling, update help dialog with new slash commands, and bump version to 1.14.0.
Introduce a new Config class in snek/system/config.py that reads environment variables, including SNEK_REGISTRATION_OPEN, to control whether user registration is allowed. The Application now loads this config and passes it to templates and views. The register.html template conditionally shows either the registration form or an invitation-only message based on config.registration_open. The RegisterView returns a 403 JSON error when registration is closed. Version bumped to 1.11.0 and CHANGELOG updated accordingly.
- Fix UserPropertyModel value field name from "path" to "value" in user_property.py
- Add self.mapper.db.commit() call after upsert in UserPropertyService to persist changes
- Refactor CSS across base.css, buttons.css, inputs.css, generic-form.css, and fancy-button.js to unify dark theme with monospace font, consistent border-radius (4px), and transition effects
- Introduce new buttons.css and inputs.css stylesheets with reusable .btn, .input, .textarea, .select classes and variants (primary, secondary, danger, success, sizes)
- Update sidebar navigation styling with active state indicator and hover border-left highlight
- Revise dialog button styles to match new design system with border, background, and hover states
- Adjust generic-form and generic-field components to use new font, colors, and focus ring (box-shadow) instead of outline
The entire GIT_INTEGRATION.md file was deleted from src/snek/templates/,
removing the outdated Git integration documentation that covered repository
creation, cloning workflows, and web interface features. This file is no
longer needed as the integration details have been superseded by newer
documentation or the feature set has changed.
Add ProfilePageMapper, ProfilePageModel, and ProfilePageService to support user profile pages with slug-based routing, ordering, and publish state. Register profile page views in the application router. Implement automatic creation of a default "General Discussion" forum on startup if no active forums exist. Add bcrypt to core dependencies and pytest with asyncio support as optional test dependencies. Refactor forum event dispatch to use inspect for robust async/sync listener handling. Extend RepositoryModel with optional description field. Update SQL schema to include IP geolocation fields on user table and history_start on channel table.
The review now includes detailed analysis of every Python file in the project, covering Docker integration, Git support, system modules, and additional architectural patterns discovered during the comprehensive codebase examination.
This commit introduces a new review.md file that provides a detailed quality assessment of the Snek project, covering its modular architecture, asynchronous design, feature set, and identified weaknesses including syntax errors in serpentarium.py and sync.py, incomplete code summaries, potential security concerns with WebSocket authentication and raw SQL queries, and code quality issues.
Add `last_read_at` column to `channel_member` table and set it in `mark_as_read` service method. Implement `get_first_unread_message_uid` RPC endpoint that queries the first message after the member's last read timestamp. Introduce a "Jump to First Unread" button in the web template with `checkForUnreadMessages` and `jumpToUnread` JavaScript functions to show/hide the button and scroll to the unread message element.
- Remove unused `aiohttp_debugtoolbar` import and its commented-out setup call in `app.py`
- Increase sidebar top padding from 10px to 20px for better vertical rhythm
- Reduce heading font-size to 0.75em, add uppercase transform, letter-spacing, and margin-top
- Add `:first-child` rule to reset top margin on first sidebar heading
- Tighten list item spacing from 15px to 4px, reduce link font-size to 0.9em
- Add block display, padding, border-radius, and hover background transition to sidebar links
Implement paste event handler that respects text selection by replacing selected content with clipboard data, and wrap the operation in an undoable transaction to allow users to revert paste actions. Remove previous clipboard item type detection logic in favor of a simpler, selection-aware approach.
When rendering reply previews, video elements were being cloned from the original message, causing them to autoplay in the reply area. This change applies the same text-replacement logic already used for images and iframes, extracting the video source URL and replacing the `<video>` tag with a plain text node containing the URL.
Add logic to strip <video> tags from quoted messages by extracting their src attribute and replacing the element with a text node, mirroring the existing handling for images and iframes. This prevents embedded video players from appearing in reply previews while preserving the source URL as readable text.
The cache is now disabled by default (self.enabled = False) to prevent stale data serving until explicitly enabled. Additionally, the inline comment documenting the version calculation was removed to clean up the code.
Replace the plain dict cache with an OrderedDict-based implementation that
provides O(1) LRU eviction by re-inserting accessed items at the end.
Introduce an asyncio.Lock to ensure thread-safe concurrent access during
get/set operations. Enable caching by default (self.enabled = True) and
add import for asyncio and OrderedDict.
Remove the entire `snek.system.stats` module including its middleware, statistics structure creation, and websocket stats tracking. This eliminates the `stats_middleware` from the application middleware chain, removes the `update_websocket_stats` calls from the RPC view handler, and deletes the 129-line stats module with its time-series SVG generation and granularity-based counter logic.
- Add performance timing with time.perf_counter() to render_template method in Application class, printing elapsed seconds to stdout
- Disable cache by setting self.enabled = False in Cache.__init__ to bypass caching behavior
- Add early return in ChannelMessageService.maintenance() to skip message iteration loop
- Remove default_limit class attribute assignment in BaseMapper.__init__ and add time import
- Refactor run_in_executor to execute synchronously with _execute() call and wrap semaphore around a second _execute() call instead of using thread pool executor
- Removed trailing semicolons from arrow function bodies in hiddenCompletions object
- Fixed inconsistent indentation for _leetSpeak toggle and textarea classList assignment
- Replaced spread operator with Object.assign for allAutoCompletions getter
- Truncated extractMentions regex pattern at diff boundary
Introduce a new `NewView` class in `src/snek/view/new.py` that renders the `new.html` template, requiring authentication. The template includes a custom `ChatWindow` web component that establishes a WebSocket connection via the `Socket` class, handles channel-based messaging with real-time updates, and supports sending messages on Enter key press while allowing Shift+Enter for multi-line input.
- Register NewView at /new.html endpoint in Application router
- Fix textToLeetAdvanced to require s.length > 1 for valid leet match
- Comment out event.waitUntil(reg) in service-worker push handler to prevent potential hang
- Added console.info call to log event name and data payload in the data handler
- Changed reconnect timeout from 0ms to 4000ms to introduce deliberate delay before reconnection attempt
The change reduces the default `max_workers` for `ProcessPoolExecutor` from 5 to 1 in the `get_or_executor` method of `ChannelMessageService`, and adds a debug print statement to log the number of available executors.
The change modifies the conditional logic in `MessageList.upsertMessage()` to only remove and nullify an existing message when `data.message` is falsy, instead of doing so when `data.is_final` is true or `data.message` is falsy. This ensures that non-final messages with content are not incorrectly removed from the DOM before being updated or re-inserted.
The change limits concurrent thread execution per user to a single worker,
preventing resource exhaustion from unbounded thread creation in the
get_or_create_executor method.
Remove the module-level `executor` variable and introduce instance-level `_executor_pools` dict with `get_or_create_executor` method that creates a `ThreadPoolExecutor(max_workers=5)` per user UID. Update all `run_in_executor` calls in `render_message` and `render_message_edit` to use the per-user executor instead of the shared global one, improving isolation and resource control across concurrent user requests.
The loop iterating over avatar elements now only assigns the first matching anchor to `lastElement`, ensuring the scroll target remains the earliest occurrence instead of being overwritten by subsequent matches.
Add a debug print statement in the `send` method's exception handler to log the exception message before deleting the socket, and update the existing debug print in the message processing loop to include a distinctive prefix for easier log filtering.
The method definition was incorrectly placed at module level instead of being properly indented as a member of the SocketService class. This fix moves the async def user_availability_service declaration one level deeper to align with the class body, restoring correct Python scoping and preventing AttributeError or runtime import failures when the service attempts to register or invoke this method.
Add detailed debug-level log statements throughout the
user_availability_service method to trace execution flow, including
entry into the main loop, socket iteration, connection status checks,
user update decisions, database save operations, and sleep intervals.
Replace the basic contenteditable editor with a full modal editor supporting insert, normal, and visual modes. Add a command-line interface at the bottom for executing commands like :w, :q, and :wq. Introduce mode-specific cursor styling, selection highlighting, and keyboard-driven navigation. The editor now tracks mode state, handles mode transitions via Escape and i/v keys, and displays the current mode in a status bar.
- Added sentry-sdk to project dependencies in pyproject.toml
- Imported and initialized sentry_sdk with DSN in main() function
- Added graceful fallback with print message if sentry_sdk import fails
- Imported `random` module to support randomized delay
- Inserted `await asyncio.sleep(random.uniform(0.1, 0.4))` before `await rpc(msg.json())` in the websocket message loop
- This introduces a jitter of 100-400ms to desynchronize concurrent RPC processing from multiple clients
The `endOfMessages.after(message)` call was placed after the `isScrolledToBottom()` check, causing the scroll position to be evaluated before the new message was inserted into the DOM. This resulted in incorrect scroll behavior when messages were added, as the scroll check would not account for the newly inserted element. Moving the insertion before the scroll check ensures the scroll position is evaluated after the DOM has been updated with the new message.
The diff shows a fix in the `upsertMessage` method of the `MessageList` class in `src/snek/static/message-list.js`. The original condition `(message && data.is_final) || !data.message` was incorrectly grouping the logical OR, causing messages to be removed only when `data.is_final` was true alongside a message, or when `data.message` was falsy regardless of message existence. The fix wraps the entire condition in parentheses: `(message && (data.is_final || !data.message))`, ensuring that a message is removed only if it exists AND either `data.is_final` is true or `data.message` is falsy. A comment `// TO force insert` was added to clarify the intent of nullifying the message reference after removal.
Previously, the upsertMessage method removed any existing message element from the DOM whenever a message with the same uid was received, regardless of whether the update represented a final version. This caused premature removal of messages that were still being streamed or updated incrementally. Now the removal is gated on the `data.is_final` property, ensuring that only completed messages are replaced in the DOM while in-progress messages remain visible until their final state arrives.
Add sanitize_html calls in ChannelMessageService for both render and save paths to strip script, iframe, object, embed tags and event handler attributes from rendered HTML templates. Refactor sanitize_html in template.py to use BeautifulSoup for tag removal and attribute cleaning, replacing the previous bleach-only implementation with a new sanitize_html2 function retained for compatibility.
The whitelist_attributes filter was applied to the rendered HTML output in both
the get_by_uid and save methods of ChannelMessageService. This filter was
stripping allowed HTML attributes from the message content, which could break
formatting or functionality in rendered messages. The fix removes the filter
call, allowing the full rendered template output to be stored and returned
without attribute sanitization.
Extend EventHandler.addEventListener to accept an optional `once` parameter that auto-removes the handler after first invocation. Implement removeEventListener method to support manual removal of specific handlers and cleanup of empty subscriber arrays. Update Socket.call method to use the new once option for one-shot RPC response listeners.
- Remove the `isVisible()` method from `MessageElement` class as it was unused and caused unnecessary DOM queries
- Fix `siblingGenerated` tracking to store the actual next sibling reference instead of a boolean, preventing stale state when elements are reordered
- Move `endOfMessages` div creation before observer setup to ensure proper initialization order in `MessageList`
- Remove the unused `isVisible()` method from `MessageElement` class to clean up dead code
- Fix `siblingGenerated` tracking logic: change from boolean to storing the actual next sibling reference, ensuring proper re-evaluation when DOM changes occur
- Move `endOfMessages` element creation before the observer loop to ensure it's observed for intersection events
- Extend addEventListener to accept an optional `{ once: true }` parameter that auto-removes the handler after first invocation
- Add removeEventListener method to EventHandler class for explicit handler removal
- Clean up empty subscriber arrays by deleting the type key when last handler is removed
- Refactor Socket.call to use the new once option instead of manual handler management
Replace the global click delegation on `messagesContainer` that checked for anchor tags with `href="#reply"` by introducing a dedicated `ReplyEvent` class. The event is now dispatched from within `MessageElement` when the reply link is clicked, carrying the message text target directly. This moves the reply logic from a fragile DOM traversal to a composed, bubbling custom event, improving encapsulation and maintainability.
The double-g press handler was incorrectly scrolling to the first child of the messages container instead of the last element, causing the viewport to jump to the top of the conversation rather than the most recent message. Changed the selector from `.message:first-child` to `lastElementChild` to properly scroll to the latest message before triggering `loadExtra()`.
- Guard update_message_text handler to only upsert messages already in the local map, avoiding cross-channel text updates
- Add delegated click listener on messagesContainer for #reply links, extracting message text for reply input
- Change scrollToBottom default behavior from 'smooth' to 'instant' for immediate positioning
- Fix loadExtra to use lastElementChild and reverse fetched messages for correct prepend order
- Correct isScrolledPastHalf logic to use absolute scrollTop value for reliable detection
Refactor the maintenance method to iterate over channel messages using synchronous `self.mapper.db["channel_message"].find()` instead of the async `self.find()` generator, wrapping each message processing in a try-except block that catches exceptions, prints them, and sleeps for 0.1 seconds before continuing.
- Import and register stats_middleware in the application middleware stack
- Add prepare_stats startup hook to initialize stats data structure
- Register /stats.html GET route with stats_handler for viewing statistics
- Create new snek/system/stats.py module with time-series data collection and SVG chart generation
- Integrate websocket statistics tracking in RPC view by calling update_websocket_stats before and after method execution
The blur handler in chat-input.js now calls updateFromInput with the current value and isFinal=true before resetting, ensuring the live-typing send uses the correct isFinal flag to avoid premature message dispatch. In message-list.js, scrollToBottom anchors to block 'start' instead of 'end' to keep the terminal view stable when new messages arrive. The app.html keydown listener also receives the event parameter to properly handle Escape key detection.
The diff replaces the static message HTML template with a custom `<chat-message>` web component, reverses message rendering order in the template, updates CSS to use `column-reverse` for chat layout, and adjusts scroll logic to append new messages at the end instead of prepending at the beginning.
The uploadResponses array is initialized in the component constructor to collect file upload results. The reset() call is moved inside the conditional block that fires when all uploads are complete, ensuring the component state is cleared only after the 'file-uploads-done' event has been published with the full response data. A console.info log is added for debugging the collected responses.
- Convert ForumModel.get_threads and ThreadModel.get_posts from returning lists to async generators yielding individual records
- Replace deprecated `_order_by` and `_offset` parameters with `order_by` string syntax in service queries
- Add `get_timestamp` helper and `save` method to BaseModel for direct persistence
- Refactor ForumView endpoints to use `services.forum.get` and `services.thread.get` instead of `find_one`
- Inject request reference into app context via `setattr(self, "request", request)` in view handlers
- Restructure forum HTML template to extend app layout with breadcrumb navigation and dark theme styling
- Add `generate_uid` method using uuid4 to BaseForumService
- Expose public `notify` method for external event dispatching
- Rename `websocket_handler` to `get` in ForumWebSocketView for proper routing
- Fix WebSocket ID generation to use forum service instance instead of generic services
Add complete forum feature including ForumApplication sub-app mounted on parent, new ForumModel/ThreadModel/PostModel/PostLikeModel entities, corresponding mappers and services, event notification dispatch system, and WebSocket endpoint for real-time updates. Refactor service registry to use dynamic registration pattern.
Implement three new WebDAV property routes (PROPGET, PROPSET, PROPDEL) alongside property file lifecycle management: delete properties on file removal and move properties on file rename via shutil.
Add `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: credentialless` headers to the `cors_middleware` response in `src/snek/system/middleware.py`, with a commented-out alternative for `require-corp` to support cross-origin isolation configuration.
Reorder the fallback chain for Open Graph image and video extraction in
`src/snek/system/template.py` to check `secure_url` before `url` and `image`/`video`,
ensuring HTTPS resources are preferred when available for embedded player support.
When a file has an unknown or empty MIME type (original_format is None or empty string),
the response now defaults to "application/octet-stream" instead of sending an empty
Content-Type header, which caused browsers to mishandle the download.
- Import time_cache alongside existing time_cache_async from app.cache
- Apply @time_cache with 60-minute timeout to block_code method in MarkdownRenderer
- Fix minor whitespace formatting issues in markdown.py
The synchronous _execute call replaces the previous async pattern that used a semaphore
and run_in_executor, simplifying the execution path and removing unnecessary async overhead
for database operations that are already synchronous in nature.
The semaphore is now a class-level attribute shared across all instances, and the retry loop with exponential backoff is removed in favor of a single execution path that always acquires the semaphore. The commit call is conditionally performed only when use_semaphore is True, and the template's live-type attribute is flipped from "false" to "true" to enable live chat functionality.
- Renamed shadowed `clients` variable to `matchedClients` in `isClientOpen` function to avoid conflict with global `clients` API
- Changed push event listener to async and added `await` before `isClientOpen` call to properly handle the returned promise
The change modifies the simulateTypingWithEvents call in the STTButton class to pass a single space character instead of an empty string, ensuring the typing simulation triggers proper input event handling for interim speech-to-text results.
The diff modifies the speech-to-text button handler in `src/snek/static/stt.js` to change behavior when interim speech recognition results are received. Previously, the code replaced the input element's value with an empty string and then simulated typing events to insert the interim text. The new implementation sets the input element's value directly to the interim text and calls `document.querySelector('chat-input').sendMessage(interim)` to immediately send the recognized speech as a chat message, bypassing the typing simulation entirely. The old typing simulation logic is commented out.
The diff modifies two key behaviors in the speech-to-text button component. First, the typing simulation delay parameter is changed from 1 to 0 in the `simulateTypingWithEvents` call within the finalize path, eliminating artificial typing delay when inserting punctuated text. Second, the interim speech recognition handling is refactored: instead of displaying interim text in the input field, both contenteditable and regular input elements now clear the previous interim text by setting their value to an empty string, while the commented-out `simulateTypingWithEvents` call for interim text is uncommented and activated to handle interim updates through the typing simulation mechanism instead of direct value assignment.
Add a check for the `snek-speaker` element's enabled state before calling `speak()`. When the speaker is disabled, fall back to playing the message sound instead of attempting speech synthesis. This prevents silent failures and ensures consistent audio feedback based on user preference.
Introduce a new `snek-speaker` web component that wraps the Web Speech API. The element provides `speak()`, `toggle()`, `stop()`, and `enable()`/`disable()` methods, along with an `enabled` property. Voice selection defaults to the first male English voice found, with asynchronous voice loading handled via the `onvoiceschanged` event.
Add snek-speaker element to chat-input.js, attach click handler on ttsButton to enable speaker, and include tts.js module in app.html. In web.html, trigger snek-speaker.speak() on final channel messages to enable text-to-speech output for incoming messages.
Refactor simulateTypingWithEvents to return a Promise that resolves when typing completes, enabling callers to chain actions after the typing simulation finishes. Update the speech recognition result handler to await this promise and call finalizeMessage() on the chat-input element, replacing the previous triggerEvent('keyup', "Enter") approach. Also add line breaks after punctuation in the committed text before typing simulation.
Add "/shorts" path to allowed YouTube URL patterns in embed_youtube function. Extract metadata retrieval logic into reusable get_element_options helper that queries twitter, ograph, meta, and element tags from head_info with fallback priority.
Add `login_required = True` to ChannelDriveApiView, ChannelAttachmentView, ChannelAttachmentUploadView, ChannelView, ContainerView, DriveView, BareRepoNavigator, StatsView, StatusView, ThreadsView, UploadView, and UserView to enforce authentication on these endpoints. Set `login_required = False` on RegisterFormView to allow unauthenticated registration. Refactor `BaseService.get` to accept positional args for uid and only filter deleted_at when not explicitly provided. Remove the deprecated `DriveView222` class entirely.
The previous implementation incorrectly queried all images within the entire message div, causing duplicate or out-of-context image source concatenation. This change restricts the querySelectorAll to only images nested inside the `.text` element, ensuring each message's image sources are appended exactly once and only from the relevant content area.
Remove the '![Replied image]()' wrapper around image sources when constructing reply text in the web chat template, leaving only the raw URL to prevent broken markdown rendering in non-markdown contexts.
Extend the allowed path prefixes in embed_youtube to include "/shorts",
enabling embedding of YouTube Shorts videos alongside regular watch and
embed URLs.
The speech-to-text component previously used `document.activeElement` to determine the target element for transcription output, which failed when no input was focused. Changed the `targetEl` getter to query for a `textarea` element instead, ensuring consistent target availability. Also updated the DOM update logic to call `.focus()` on the target and assign directly to `.value` instead of using `setAttribute("value", ...)`, enabling proper text insertion into the textarea.
- Create new STTButton web component in src/snek/static/stt.js with shadow DOM, speech recognition API integration, and pulsing visual feedback during listening state
- Add stt.js script import to app.html template to register the custom element globally
- Instantiate and append stt-button element inside ChatInputComponent constructor in chat-input.js
- Add CSS class "chat-input-textarea" to the textarea element for styling hooks
- Attach change event listener on textarea to sync value and trigger updateFromInput on manual edits
The update_message_text method in RPCView was refactored to remove the no_save context wrapper and streamline validation logic. The commented-out time-based restriction check was also removed, allowing message updates without the previous 8-second window limitation.
Add a unique index on the `uid` field in the `channel_message` collection to enforce document uniqueness at the database level. In the RPC message update handler, remove the 8-second time-limit check and instead validate that the message is not marked as final or deleted before allowing edits.
Add lazy index creation for `['is_final','user_uid','channel_uid']` and `['deleted_at']` fields in `ChannelMessageService.save()` method, guarded by a `_configured_indexes` flag to ensure indexes are created only once per service instance.
- Added `deleted_at=None` filter to `check_message` query in `send_message` to exclude soft-deleted messages from finalization queue
- Added `is_final=False` and `deleted_at=None` filters to `get_message` in `update_message` to prevent editing finalized or deleted messages
- Added early return with error response when message is not found after applying new filters
- Added conditional finalization logic: only queue finalization for non-final messages, cancel existing finalization task for already final messages
The previous implementation used a single `await asyncio.sleep(7)` which could not be interrupted
mid-wait when the finalize task was cancelled. The new approach loops 7 times with 1-second sleeps,
checking `self._finalize_task` after each second and returning early if the task reference is cleared,
plus catching `CancelledError` to handle explicit cancellation gracefully.
When message is empty after processing check messages, the handler now returns early
instead of proceeding to send an empty message through the chat service. This prevents
unnecessary API calls and potential errors from sending blank content.
- Comment out direct scrollTop assignment in scrollToBottom method and use scrollIntoView on the bottom anchor element instead
- Change display toggle from messageDiv to textElement in updateMessage to correctly hide empty text content while preserving message container layout
- Move display toggle from textElement to messageDiv in message-list.js to properly hide entire message container when text is empty
- Remove is_final=False filter in _finalize_message_task to allow retrieval of already finalized messages
- Add early return when message is already finalized to prevent duplicate finalization attempts
- Strip whitespace from incoming message in send_message to handle empty/whitespace-only inputs
- Remove unused variable assignment for is_final in send_message non-final branch
Remove the unused `expiryTimer` property and its related clearTimeout calls from ChatInputComponent. Fix the `liveType` boolean assignment by changing the comparison operator from `!==` to `==` so that the attribute value `"true"` correctly enables live typing behavior. Make `finalizeMessage` synchronous by removing the `await` keyword from the RPC call and the method declaration. Remove the early return guard for empty message values in `sendMessage` to allow empty strings to be sent. Add server-side logic for finalizing messages after a 7-second delay via a new `_finalize_message_task` coroutine, with cancellation support through `_queue_finalize_message`. Re-enable the previously commented-out logic in `send_message` that checks for an existing non-final message from the same user and updates it instead of creating a new one.
The href attribute of the "Sign Up" anchor element in the index.html template was changed from "/register" to "/register.html" to align with the static HTML routing convention used elsewhere on the site, ensuring consistent URL resolution for the registration page.
- Extend embed_youtube hostname check to include music.youtube.com subdomain
- Skip anchor elements containing data-noembed attribute in embed_url to prevent unwanted embedding
Add `target`, `rel`, `referrerpolicy`, `controls`, `frameborder`, `allow`, and `allowfullscreen` to the SAFE_ATTRIBUTES set in `src/snek/system/template.py` to prevent stripping of these commonly used attributes during HTML sanitization. Also fix a syntax error in the `embed_url` function where an f-string used double quotes inside a double-quoted string, changing it to single quotes for the fallback description text.
The title attribute was removed from the SAFE_ATTRIBUTES dictionary
in src/snek/system/template.py, likely to tighten security by
preventing arbitrary title attributes in sanitized HTML output.
Also fix string interpolation syntax in embed_url function where double quotes were incorrectly nested inside f-string, changing outer quotes from double to single to resolve syntax error.
Extract repeated meta-tag lookup logic into reusable get_element_options function that accepts fallback priority chain (twitter, og, meta, elem). Enhance embed_url to skip already-processed URLs via attachment key check, and extend embed payload with optional height/width attributes from og:image:width/height or twitter:image:width/height. Refine site name selection to prefer og:site_name over twitter:site, falling back to title tag.
Add `history_start` field to ChannelModel to support filtering messages by creation date, implement `clear` method in ChannelService to set history cutoff, and introduce `no_save` async context manager in Application that patches channel_message save to only update cache. Refactor ChatService to reuse existing non-final messages when updating drafts, and fix UserService search to filter out deleted users.
Extend the embed_youtube function's hostname whitelist to include "music.youtube.com" for YouTube Music URL support. Also update embed_url to skip anchor elements that have a "data-noembed" attribute, preventing unwanted embedding of explicitly excluded links.
- Invert the live-type attribute check from `=== "true"` to `!== "true"` in ChatInputComponent
- Comment out the non-final message lookup logic in RPCView.send_message and force is_final to always be True
Previously, the `send_message` method always queried for an existing non-final
message to update, even when `is_final=True`. This caused unnecessary database
lookups and could incorrectly attempt to update a stale message when sending
a final version. The fix wraps the continuation check inside `if not is_final:`,
so the lookup and update path is only taken when the message is explicitly
marked as non-final.
The `send_message` method in `RPCView` had a local variable `message` that shadowed the `message` parameter, causing the parameter to be overwritten before being passed to `update_message_text`. Renamed the local variable to `check_message` to preserve the original parameter value for downstream use.
- Introduce EventBus class in njet.js with subscribe/publish methods for decoupled component communication
- Replace direct NjetComponent subscription with eventBus.subscribe in chat-input.js for file-uploading events
- Add file-uploads-done event handler that reconstructs message from uploaded files and sends via RPC
- Reset uploadResponses array in FileUploadGrid.reset() to track completed uploads
- Publish file-uploads-done event when all uploads finish, passing array of file metadata
- Send relative_url in WebSocket done message from ChannelAttachmentUploadView for client-side link construction
- Modify RPC send_message to check for existing draft message and update it instead of creating new one
Removed four debug print calls that were logging function arguments, table name, and
result during the database operation retry loop in the BaseMapper class. These prints
were left over from development and were cluttering the output during normal operation.
Add `is_final` field to channel messages with a maintenance loop that toggles messages between final and non-final states. Introduce `send_message` RPC endpoint for creating messages with configurable finality. Remove `_finalize_message_task` and `updated_at` override in chat service, simplifying message finalization logic. Add debug prints to mapper retry loop for database operations.
- In `src/snek/service/chat.py`, wrap non-final channel message saves inside `self.app.no_save()` to prevent persistence of intermediate streaming updates, while final messages save normally.
- In `src/snek/view/rpc.py`, change `session_get` to be async and require a default argument, removing the optional default and fixing the internal lookup to use direct dict access instead of `.get()`.
- Remove unused `datetime` and `time` imports, reorder `_finalize_task` initialization after `_scheduled` list.
The change adds a call to `self.db.commit()` immediately after each successful execution of the wrapped function within the 20-retry loop in `BaseMapper.__call__`. Previously, the function result was returned directly without committing, which could leave pending database transactions unflushed. Now the commit ensures data persistence before returning the result, while still preserving the existing retry-on-exception behavior.
- Inject CSP nonce into request context via middleware and pass to templates for script integrity
- Refactor CSP policy to inline nonce generation and update allowed sources (umami, stricter style/img/connect rules)
- Fix editor.js import path from `/njext.ks` to `/njet.js`
- Add `clear` method to ChannelService to reset `history_from` timestamp
- Add `clear_channel` RPC handler for admin users to purge channel history via `ChannelMessageService.clear`
- Insert `asyncio.sleep(0)` in BaseMapper retry loop to yield control during exception handling
- Introduce `no_save` async context manager in Application that temporarily replaces the channel_message mapper's save method with a no-op that only updates cache, allowing operations to bypass persistence while tracking suppressed saves
- Enable cache by default in Cache class (set `self.enabled = True`) to support cached reads in service layer
- Add retry logic (up to 20 attempts) in BaseMapper's semaphore-guarded execution to handle transient database failures
- Modify `get` method in BaseService to always filter by `deleted_at is None` and return cached results when available, improving performance for active records
- Update `update_message_text` RPC handler to wrap logic in `no_save` context, increase message age threshold from 3 to 5 seconds, and remove explicit save call to prevent redundant writes
Implement a custom web component NjetEditor that extends NjetComponent, providing a vim-style editor with normal, insert, visual, and command modes. Includes key bindings for navigation (h,j,k,l,w,b,0,$,gg,G), editing (i,o,a,x,dd,yy,p), visual selection, and a command-line interface triggered by ':'. The editor uses a contenteditable div with dark theme styling and manages internal state for mode, key buffer, yanked/deleted lines, and caret position tracking.
- Add optional `history_start` field to ChannelModel for filtering messages by creation date
- Update `get_last_message` and `offset` methods in channel_message service to apply history_start filter when set
- Fix user search to filter out deleted users by setting `deleted_at` to None
- Fix typo `cconsole.info` to `console.info` in file-upload-grid.js
- Refactor `run_in_executor` in BaseMapper to conditionally use semaphore and add deleted_at filter to get method
- Add `editor.js` script to app.html template
- Add `clear_channel` RPC endpoint for admin users to delete all messages in a channel
Uncomment click handler in ChatInputComponent to trigger file dialog, add console.info statements in FileUploadGrid for file upload lifecycle events, comment out redundant click listener in UploadButtonElement, and add print debugging in ChannelAttachmentUploadView for binary/text WebSocket messages with async file.tell() call.
Introduce a new stylesheet defining `.njet-dialog` and `.njet-window` classes, both using fixed positioning, 50% centering via translate, 20px padding, and a 33px min-width.
Introduce ChannelDriveApiView subclassing DriveApiView to serve per-channel drive listings under `/channel/{uid}/drive.json`. Refactor DriveApiView into reusable base methods (get_target, get_download_url) for subclass override. Update file-manager.js to accept a configurable `url` attribute, defaulting to `/drive.json`, and import NjetComponent for future composition. Register the new route in Application and wire channel.html toggleDrive to mount/unmount a file-manager element pointed at the channel drive endpoint.
- Replace NjetDialog import with njet import in channel.html template
- Change deleteChannel function to show upload-in-progress dialog instead of delete confirmation
- Add dialog.open() call in file-upload-grid.js to ensure dialog is displayed
- Add ChannelAttachmentUploadView with WebSocket endpoint at /channel/{channel_uid}/attachment.sock for streaming file uploads with progress tracking
- Remove SIGINT/SIGTERM signal handlers that were calling container shutdown
- Fix chat-input.js to open file dialog on upload button click instead of relying on uploaded event
- Fix file-upload-grid.js import path from relative to absolute and move connectedCallback before render to capture channel attribute
- Update ComposeFileManager.shutdown to collect stop tasks asynchronously instead of awaiting each sequentially
The cp command in DockerfileUbuntu had two errors: the source path was incorrectly
specified as './root' instead of '/root', and the target directory was misspelled
as '/opt/bootrap/root' instead of '/opt/bootstrap/root'. This fix ensures the
bootstrap root files are properly copied during image build.
Add SIGINT and SIGTERM signal handlers to the Application class that trigger
a graceful shutdown of all running docker compose sessions via the new
ContainerService.shutdown() method. The ComposeFileManager.shutdown()
method iterates over running instances and stops each process gracefully
if it is still running.
- Change container user from 'ubuntu' to 'root' in Dockerfile and docker-compose config
- Set working directory to /home/retoor/projects/snek and inject SNEK_UID environment variable
- Add root password setup and copy terminal bootstrap files (entry script, .welcome.txt) into container image
- Refactor compose exec/start methods to capture stdout/stderr via pipes and handle empty output gracefully
- Remove strict returncode check in exec method, replacing with debug print for non-zero exits
The write_stdin method in RPCView was calling content.encode() before passing to the container service, but the service already handles encoding internally. This change removes the duplicate encoding to prevent double-encoding issues when writing to container stdin.
Implement async write_container method in RPCView class that validates user login and channel membership, retrieves container name, writes encoded content to container stdin, and returns a placeholder response indicating terminal write completion without response handling.
The container service's volume mount configuration for the home folder was pointing to an incorrect base path. Changed the hardcoded path from "/home/ubuntu" to "/root" to match the actual container filesystem layout, ensuring proper volume binding when creating container instances.
Adjust the terminal container width from 40% to 50% and the chat area left position from 50% to 60% to improve layout proportions and visual balance between the two panels.
Add a `fit()` method to the Container class that invokes the xterm.js fit addon to resize the terminal to its parent element. In the app template, introduce a `toggleDevelopmentMode()` function that hides the header, repositions the sidebar to a fixed 10% width on the left, allocates 40% width to both the terminal and chat area side by side, calls `container.fit()` for proper terminal sizing, and renders a "H4x0r 1337" word on the star field. Also refactor the `/container` command in web.html to remove redundant container initialization logic, leaving only a placeholder comment for the dialog open call.
Add keyboard event handler for Ctrl+. key combination that toggles the
hidden state of the terminal element when window.container is available,
providing a quick way to show or hide the terminal interface during runtime.
Remove the non-functional `toggleContainer` method and its associated Ctrl+. keyboard shortcut that referenced undefined variables. Relocate the channel initialization block after the keydown listener to fix execution order dependency. Simplify `getContainer` to return a new Container instance directly without caching.
Implement a toggleContainer function that shows/hides the container terminal element, and bind it to Ctrl+. keypress. Refactor getContainer to cache the Container instance and expose it globally. Move channel initialization block earlier in the script to ensure container is available before key handlers. Change container start response from string to bytes in ContainerView.
Implement a full-featured HTTP client class in njet.js supporting configurable base URL, default headers, request/response interceptor chains, automatic JSON serialization/deserialization, and response header parsing. The class provides a core request method that merges configuration, applies interceptors via promise chaining, and handles both successful and error responses with structured result objects.
- Import Njet and NjetComponent in app.js and chat-input.js, extend ChatInputComponent from NjetComponent
- Add FileUploadGrid component with CSS styling, progress bars, and file dialog handling
- Integrate file upload grid into chat input with subscribe-based visibility toggling
- Include new CSS and JS module scripts in app.html template
- Add channel.html template with NjetDialog for delete channel confirmation
Implement a new `load_env` function in `src/snek/__init__.py` that reads key-value pairs from a specified environment file (defaulting to `.env`), strips whitespace, skips empty lines and comments, and sets each variable in the current process environment via `os.environ`. Includes full MIT license header with author attribution and comprehensive docstring with error documentation for `FileNotFoundError` and `IOError`.
Implement StatisticsService with PRAGMA table_info and COUNT queries across 14 tables, printing column stats including distinct counts and numeric min/max/avg. Add Shell class that initializes Application from a SQLite path and exposes maintenance coroutines via IPython user namespace.
- Add `maintenance` CLI command that runs container and channel message maintenance routines
- Implement `ContainerService.maintenance()` to create containers for channels missing them
- Implement `ChannelMessageService.maintenance()` to re-render and upsert outdated messages
- Register `StatisticsService` in service registry for future metrics collection
- Refactor `shell` command to use dedicated `Shell` class instead of inline IPython setup
- Make `ContainerService.create()` idempotent by returning existing instance if present
- Fix `ComposeFileManager.get_instance()` to return `None` for missing services instead of crashing
The condition for determining if a Docker Compose instance is running previously checked
`returncode == 0`, which incorrectly treated successfully exited processes as still running.
Changed to `returncode == None` to properly detect processes that are still active, as a
None returncode indicates the process has not yet terminated.
The default compose dictionary no longer includes the deprecated "version" key,
keeping only the "services" entry to align with modern Docker Compose specs.
- Relocated xterm CSS and JS imports from app.html to web.html to ensure terminal dependencies are only loaded on pages that actually use the terminal component
- Changed stream.readline() to stream.read(1024) in docker.py's ComposeFileManager to read fixed-size chunks instead of line-buffered reads for binary stream handling
- Added conditional rendering of channel UID and container initialization in app.html to prevent JavaScript errors when no channel context exists
- Removed debug print statements and redundant json.loads/json.dumps serialization in get_instance method to clean up logging and fix instance object handling
Add ContainerView websocket endpoint at /container/sock/{channel_uid}.json that streams container stdout/stderr to client and forwards stdin input. Introduce ContainerService.write_stdin method and refactor event listener system from queue-based to handler-based with automatic cleanup on handler failure. Implement client-side Container class using xterm.js terminal with FitAddon, connecting via binary WebSocket to display real-time container output. Update ComposeFileManager to create stdout/stderr readers for running containers and serialize instance data with json.dumps for proper type handling. Include hidden terminal div in web template and wire /container command to instantiate and render terminal on first invocation.
Implement asynchronous start and stop methods in ComposeFileManager for individual containers via docker compose commands, along with a container_event_handler callback that dispatches events to registered asyncio queues. Add corresponding start_container and stop_container RPC endpoints in RPCView, each guarded by channel membership authorization. Introduce event listener registration in ContainerService to support future event-driven workflows.
Replace direct mapper CRUD methods on ContainerService with compose-based get and get_status that asynchronously query docker compose ps for running/stopped state. Introduce a new ContainerDialogComponent web component with disabled button styles, a properties table, and start/stop/restart controls. Wire the /container command in the chat input to open the dialog with live status. Add RPC endpoints get_container and get_container_status that verify channel membership before returning container metadata and status.
Refine the page title and meta description/keywords/OG tags to shift the audience focus from "enthusiasts" to "professionals", and rephrase the description to emphasize secure collaboration, rapid onboarding, and audit-free operation.
Add a new about.html page with a sticky footer using flexbox layout, updated meta description, and refined hero copy. Include .about-link styled anchors on index.html in the header, features section, self-host section, and footer to improve site navigation and user awareness of the about page.
Add a full-width top navigation bar with home link, hover/active states, and responsive mobile layout. Remove the previous centered footer element and its associated CSS rules. The new nav uses a dark background, flexbox alignment, and includes an icon alongside the home link.
Replace the Jinja2 template inheritance in about.html with a complete standalone HTML5 document featuring inline CSS, gradient hero section, and structured content sections. Enhance index.html with comprehensive Open Graph, Twitter Card, and canonical meta tags including targeted keywords for developers, testers, and AI enthusiasts.
- Inserted `<link rel="stylesheet" href="/sandbox.css" />` into the `<head>` of `base.html` to load sandbox-specific styling
- Added `{% include "sandbox.html" %}` inside the `<main>` block of `base.html` to render the sandbox component on every page
- Replaced the entire inline `<style>` block and full-page HTML structure in `index.html` with a minimal template, removing duplicated layout and CSS definitions
Reorganized import statements in __main__.py, app.py, balancer.py, mapper/__init__.py, model/__init__.py, and several model files to follow PEP8 conventions with grouped standard library, third-party, and local imports. Applied consistent formatting with black-style line wrapping, added missing trailing commas, and fixed whitespace issues in function signatures and class definitions.
Introduce a new ComposeFileManager class for managing Docker Compose YAML files, enabling instance creation, removal, duplication, and listing. Refactor ContainerService.create to accept channel_uid and delegate to ComposeFileManager, mounting the channel's home folder as a volume. Add get_home_folder method to ChannelService that ensures the home directory exists under ./drive/{channel_uid}/container/home. Update channel creation flow to automatically call container.create with the new channel UID. Enhance the web template with a welcome message for empty channels, displaying basic command hints.
- Add async call to `self.services.notification.create_channel_message(message_uid)` after storing each channel message
- Ensure notification service is invoked with the message UID for downstream alerting
The `send` method in `NotificationService` now checks the `is_final` flag on the
`channel_message` record before proceeding with notification dispatch. If the
message is not marked as final, the method returns early without sending
notifications to channel members. This prevents premature or duplicate
notifications for messages that may still be updated or processed further.
The notification service's `_notify_channel_members` method was calling `channel_member.get_name()` without the `await` keyword, causing a coroutine object to be passed instead of the resolved string value. This resulted in push notifications containing the string representation of the coroutine object rather than the actual channel member name. The fix adds the missing `await` to properly resolve the async method call.
Remove the CDN link for font-awesome 6.4.0 all.min.css and replace the single
32x32 favicon with two separate favicon links for 32x32 and 64x64 sizes using
the new snek_logo naming convention.
Add snek_logo images at 16x16, 32x32, 64x64, 128x128, 256x256, 640x480, 800x600, and 1024x1024 resolutions to the static image directory. Update manifest.json to include 144x144, 256x256, and 1024x1024 icon entries alongside existing 192x192 and 512x512 references.
- Add strip_markdown() function to snek/system/markdown.py that removes code blocks, headings, images, links, emphasis, blockquotes, and special characters from markdown text
- Import and use strip_markdown in NotificationService to strip markdown from push notification messages
- Add new logo image files at 48x48, 72x72, 96x96, 144x144, 192x192, 384x384, and 512x512 sizes
- Update manifest.json icon references to use new snek_logo_192x192.png and snek_logo_512x512.png paths
- Fix channel_member reference in notification title from label to object representation
Include the minified Font Awesome 6.4.0 CSS bundle (fa640.all.min.css) as a new static asset and link it in the app.html template before the base stylesheet, enabling icon support across the application.
The csp_middleware function had a `return response` statement before the response object was assigned, causing the Content-Security-Policy header injection code to be unreachable. Moved the return statement after the header assignment to ensure CSP headers are properly applied to all responses.
The middleware function csp_middleware in src/snek/system/middleware.py was modified to include a premature `return response` statement before the actual handler invocation and CSP header injection logic. This change effectively bypasses the Content-Security-Policy header generation and nonce creation, causing the middleware to return an unprocessed response object without security headers.
Introduce a constant `csp_policy` defining a Content Security Policy template with a `{nonce}` placeholder for script-src, and retain the existing `generate_nonce` helper for producing cryptographically random nonces. This prepares the middleware to inject per-request nonces into HTTP responses for improved XSS protection.
The middleware function signature was updated from `(app, handler)` to `(request, handler)` to match the expected aiohttp middleware interface, ensuring the Content-Security-Policy header is properly applied to responses.
Implement a new CSP middleware that generates a cryptographically random nonce per request and attaches a Content-Security-Policy header to every response. The middleware is registered in the Application middleware stack after ip2location_middleware, and the csp_middleware import is added to the module imports in app.py. The nonce is produced via secrets.token_hex(16) in the new middleware module.
Add 'form' and 'input' to the list of HTML elements that are stripped from
sanitized output alongside existing 'script' tag handling. Include a continue
statement after replacement to prevent redundant attribute processing on
removed elements.
Add whitelist_attributes call to ChannelMessageService.save to filter allowed HTML attributes on rendered message content. In template.py, skip attribute processing for non-Tag elements and remove script tags entirely by replacing them with empty string before attribute whitelisting.
Add `bleach` to project dependencies in pyproject.toml and implement a `sanitize_html` function in `src/snek/system/template.py` that uses bleach to strip unsafe HTML tags and attributes. Register the function as a `sanitize` Jinja2 filter in `src/snek/app.py` to allow template output sanitization.
The `_allow_harmful_protocols` flag in `src/snek/system/markdown.py` was toggled from `True` to `False`, preventing the markdown renderer from permitting potentially dangerous URL protocols during HTML rendering.
Add CSS rules for `.message-content .spoiler` to hide child content via opacity, pointer-events, and visibility, and reveal it on hover/focus/active with a 0.3s opacity transition. Include a `delay-pointer-events` keyframe animation to prevent premature interaction during the reveal. Set spoiler container to a fixed 2.5rem height with overflow hidden, expanding on interaction.
- Reformatted empty arrow function bodies in autoCompletions to use block syntax
- Removed unused properties (previousValue, lastChange, changed) from ChatInputComponent
- Added expiryTimer property and removed redundant constructor assignments
- Simplified set value method by removing fallback to empty string
- Refactored resolveAutoComplete to use for-of loop and early return on duplicate matches
- Updated starsRender call formatting for consistency
Remove unused `previousValue`, `lastChange`, and `changed` properties from ChatInputComponent constructor that were causing state desynchronization. Refactor `resolveAutoComplete` method to return null on multiple matches instead of silently picking the last one, and add `expiryTimer` property for future timeout management. Clean up formatting inconsistencies across the component.
Add a guard condition in the notification creation flow to compare
channel_member["user_uid"] against user["uid"] before calling
notify_user, preventing the sender from receiving a push notification
for their own message.
The middleware was missing a return statement for the response, causing the handler to never complete and ip processing code to be unreachable. Added `return response` before the ip extraction and geolocation logic to ensure proper middleware chain execution.
Add `update` method to `BaseMapper` that updates a model's `updated_at` timestamp and persists the record using the table's update operation keyed by `uid`. Expose this through `BaseService.update` to provide a consistent service-layer interface for model updates.
Add `ip` field to `UserModel` schema and populate it from the request IP in `ip2location_middleware` to persist the user's IP address alongside location data.
Add IP2Location dependency and binary database for IP geolocation lookups.
Introduce ip2location_middleware that automatically updates authenticated
users' location data (country, city, region, coordinates) based on their
request IP address, skipping private IPs and unauthenticated sessions.
Extend UserModel with new optional fields: country_short, country_long,
city, latitude, longitude, and region to persist the geolocation data.
Introduce an asyncio.Semaphore(1) and a run_in_executor method in BaseMapper, wrapping synchronous table operations (find_one, exists, count, upsert) to prevent blocking the event loop. Add a loop property returning the current event loop.
The commit introduces a per-instance asyncio.Semaphore(1) in BaseMapper.__init__ and wraps the run_in_executor method with an async with self.semaphore block, replacing the previous synchronous direct call with an actual thread pool execution via loop.run_in_executor. This ensures that concurrent calls to run_in_executor are serialized, preventing race conditions when the mapper interacts with blocking I/O or shared resources, while also restoring the intended asynchronous offloading behavior that was previously commented out.
Add asyncio import and loop property to BaseMapper, introduce run_in_executor helper method, and convert all blocking database operations (find_one, exists, count, upsert, find) to async wrappers that offload work to the default executor thread pool
Add asyncio import and run_in_executor helper to BaseMapper class, converting all synchronous database calls (find_one, exists, count, upsert) to async by executing them in the default thread pool executor via the event loop.
Remove unused `memberships` and `user` variables, eliminate debug print statements for headers, and migrate the HTTP POST call from the blocking `requests` library to an `aiohttp.ClientSession` with async context managers. Adjust response attribute access from `.status_code` to `.status` to match aiohttp's API.
Add a PEM-encoded X.509 certificate (cert.pem) and its corresponding RSA private key (key.pem) to enable HTTPS for debug and testing environments. The certificate is self-signed with a 1-year validity period, issued to "Internet Widgets Pty Ltd" in the Netherlands, and includes basic constraints for CA usage.
- Introduce PushMapper and PushRegistrationModel for storing user push subscription data (endpoint, key_auth, key_p256dh)
- Implement PushService replacing old Notifications class, with async notify_user method sending WebPush payloads via aiohttp
- Register PushService in service registry and PushMapper in mapper registry
- Update NotificationService to trigger push notifications on new channel messages
- Enable SSL context in main app runner loading cert.pem and key.pem
- Remove deprecated get_notifications calls and old notification module
- Refactor PushView to use new PushService for key retrieval and subscription registration
- Add await navigator.serviceWorker.ready in push.js to ensure service worker readiness before fetching public key
The decorator on `get_url_content` in `src/snek/system/template.py` was changed from `@lru_cache(maxsize=128)` to `@time_cache(timeout=60*60)`, and a new import `from app.cache import time_cache` was added. This ensures cached URL content expires after 3600 seconds instead of persisting indefinitely up to a fixed cache size.
Implement embed_url function that fetches remote pages, extracts Open Graph and Twitter Card metadata (title, description, image, video, site name), and renders embedded preview cards for links in chat messages. Add LRU-cached HTTP fetching via requests, parse head meta tags with BeautifulSoup, and append embed HTML after anchor elements. Include responsive CSS for embed containers with max-height 400px media, rounded top corners, and styled text elements for site name, page title, description, and link.
Initialize users array, textarea reference, _value, lastUpdateEvent, previousValue, lastChange, and changed properties in class body before constructor, and remove redundant _value initialization from constructor.
Clean up the import block in src/snek/system/template.py by removing
the unused `asyncio` and `aiohttp` modules, and reordering remaining
imports to follow standard grouping conventions (stdlib, third-party,
local).
Introduce a `hiddenCompletions` property on ChatInputComponent to support privileged slash commands like `/starsRender`, merged with existing auto-completions via `allAutoCompletions`. Modify `resolveAutoComplete` to match only the first word of input, enabling multi-word command arguments. Update the Enter key handler to invoke the hidden completion handler directly instead of setting the textarea value. Add a `stars_render` RPC method that broadcasts a `stars_render` WebSocket event to all online users in a channel, and a corresponding client-side listener that renders the message as a rainbow word on the star field with a delayed shuffle. Increase the deployment notification delay from 10 to 15 seconds to accommodate the new render sequence.
Add keyboard event handler that intercepts the 'i' key press and focuses the chat input field, but only when the input field is not already active. This provides a quick keyboard shortcut for users to start typing without clicking the input area.
- Set `live-type="true"` on chat-input element in web.html template
- Move `finalizeMessage` call from Enter key handler to `updateMessage` method in chat-input.js
- Add `_debug` flag toggling in App class with debug method and socket debug logging
- Change socket event listener from "event" to "data" with conditional debug output
- Fix channel_uid reference in chat.py broadcast call
- Add scroll-to-bottom behavior after message text update in message-list.js
- Remove console.info statements from message-list.js scroll methods
- Add null check for message in finalize_message RPC handler and return boolean
The `.ssh` directory may not exist when the server starts, causing a `FileNotFoundError` because `mkdir` without `parents=True` fails if intermediate directories are missing. Added `parents=True` to ensure the full path `drive/.ssh/` is created recursively.
Add `is_final` boolean field to ChannelMessageModel with default true, extend ChannelMessageService.create to accept and store the flag, implement ChatService.finalize method that marks a message as final and broadcasts its complete data, and update RPCView with finalize_message endpoint that validates ownership before finalizing. Modify chat-input.js to pass `is_final` parameter based on live typing state and trigger finalization on Enter key press.
The original code called sorted() without assignment, leaving the results list unsorted. This fix assigns the sorted list back to the results variable, ensuring the returned list is correctly ordered by user nick.
The `get_recent_users` query in `channel.py` now includes a `WHERE` clause filtering `user.last_ping` to within the last 3 minutes, replacing the previous unfiltered `get_online_users` call. The `rpc.py` handler removes manual deletion of sensitive fields (`email`, `password`, `deleted_at`, `updated_at`) and adds sorting of results by the `nick` key.
Remove all explicit `background-color: #000000` declarations from base.css across header, sidebar, chat-header, chat-input, input fields, footer, and main content areas. Add `z-index: -1` to the star pseudo-element in sandbox.css to ensure stars render behind other page content without interfering with layout.
Implement a new `get_recent_users` RPC method that returns the 30 most recently active channel members ordered by `last_ping`, backed by a new `ChannelService.get_recent_users` query joining `channel_member` with `user` table. Update the chat input component to call `getRecentUsers` instead of `getUsers` on initialization, and extend the mention extraction regex to support hyphens in usernames (`@([a-zA-Z0-9_-]+)`). Remove a debug `console.warn` call from the mention distance calculation logic.
The levenshtein distance penalty for mentions that are not a subsequence of the author name was increased from 1 to 10, making non-subsequence matches significantly less likely to be selected as the closest author. Additionally, the console.info logging was moved outside the minDistance update block to log the distance for every mention comparison, not just when a new closest author is found.
The change modifies the `distance` variable declaration inside the `ChatInputComponent` autocomplete logic from `const` to `let`, enabling reassignment when the mention is not a subsequence of the author name. This ensures the distance penalty is correctly applied during user mention filtering.
The levenshtein distance calculation for author mentions now includes a subsequence check to penalize matches where the mention text is not a subsequence of the author name. This prevents false positive matches where characters are present but in incorrect order, improving the accuracy of author mention suggestions in the chat input component.
The diff removes the `lastMessage` variable declaration and its associated DOM query (`messagesContainer.querySelector(".message:last-child")`) from two locations in the web.html template: the channel-message event handler and the updateLayout function. This variable was previously assigned but never referenced elsewhere in the code, making it dead code that can be safely eliminated to reduce clutter and improve maintainability.
The user fetch in chat-input.js now uses a non-blocking `.then()` callback
instead of `await`, allowing the component to render without waiting for
the RPC call to resolve.
Move the isElementVisible utility into the MessageList class as a method and add a new isScrolledToBottom method that checks visibility of the .message-list-bottom element. Update the channel-message event handler to call messagesContainer.isScrolledToBottom() instead of the removed global function, ensuring scroll-to-bottom behavior is correctly determined after new messages arrive.
When a channel member is not found for the current user, automatically create a new membership record with default permissions if the channel is not private, instead of immediately returning a 404 error. This ensures users are properly joined to public channels they access.
The inline click handler for image zoom overlay creation and removal was moved from the event listener registration block into a standalone function, improving code modularity and reusability.
Implement a fullscreen overlay for image clicks in the chat message list, excluding avatar images. The overlay displays the clicked image without query parameters, centered on a dark background, and removes itself on click.
Extend the `save` method in `ChannelMessageService` to fetch the associated user record via `user_uid` and merge user fields (`uid`, `username`, `nick`, `color`) into the template rendering context, replacing the previous direct use of `model.record`.
- Create new `avatar_animal_view.py` module with `AvatarView` class inheriting from `BaseView`
- Implement `get` method that accepts a `uid` parameter from request match info
- Integrate `multiavatar` library for avatar generation alongside existing `generate_avatar_with_options` helper
- Set `login_required = False` to allow public access to avatar generation endpoint
- Initialize `self.avatars` dictionary in constructor for potential caching of generated avatars
- Changed body layout from flexbox to CSS grid with explicit grid-template-areas for header, sidebar, and chat-area
- Added `height: 100%` to html element and changed body height from `100vh` to `100%` to prevent mobile browser chrome issues
- Updated viewport meta tag with `interactive-widget=resizes-content` to handle virtual keyboard resizing on mobile
- Moved main element after sidebar in DOM order and assigned grid-area to main and header
- Added `.chat-messages` flex container and fixed indentation inconsistencies across CSS selectors
- Changed umami analytics script from `defer` to `async defer` for non-blocking loading
Replace flexbox body layout with a CSS grid using named template areas (header, sidebar, chat-area) to improve mobile responsiveness and sidebar positioning. Update viewport meta tag to include `interactive-widget=resizes-content` for better mobile keyboard handling. Add `html { height: 100% }` and `.chat-messages { display: flex; flex-direction: column }` to support proper vertical sizing. Move `<main>` element after sidebar block in app.html template to match new grid structure. Change umami analytics script from `defer` to `async defer` in base.html.
The change adds inline style `max-height: 100vh; overflow-y: none` to the `<main>` element in `src/snek/templates/app.html`, preventing the main content area from exceeding the viewport height and disabling vertical scrolling within it. This ensures the layout stays within the visible screen bounds, likely to support a fixed sidebar or header layout without page-level scroll.
- Add redis caching for generated avatars using key prefix "avatar_animal_"
- Handle "unique" uid by generating a random uuid4 to bypass cache
- Remove in-memory avatars dict and _get method's caching logic
- Return cached avatar immediately if found in redis before generating new one
The avatar endpoint was broken after a previous refactor changed the type parameter source from query string to URL path match_info. This commit reverts that change in the `get` method of `AvatarView` class, restoring the original behavior where `type` is read from `self.request.query.get("type")` instead of `self.request.match_info.get("type")`. This fixes the 404 errors and missing avatar images for animal and default avatar types.
Add new GET route "/c/{channel:.*}" mapped to ChannelView for channel support. Move parent_object retrieval before other_user and last_message lookups in channel member subscription loop to ensure data dependencies are resolved in correct order.
Implement ChannelView handler that resolves channel by name with multiple lookup attempts, creates a new channel if none found using query parameters for visibility flags, assigns creator as moderator, and redirects to the channel's HTML page.
The fix addresses a race condition where pressing Enter to send a message while live typing was active would trigger both the submit handler and the live typing update, resulting in the same message being sent twice. The solution reorders the cleanup sequence in the submit handler to clear the input value before calling updateMessage, and conditionally assigns the messageUid only when live typing is enabled in the sendMessage callback.
The fix reorders operations in the submit handler to clear the input field value before calling `sendMessage`, and moves the `messageUid` assignment into the live-typing path only, eliminating the double-send that occurred when both the submit handler and the live-typing timer triggered message transmission.
Refactor getAuthors() to fetch users from app.rpc.getUsers() instead of scanning DOM elements. This improves performance and reliability by using the server-side user list, which includes both username and nick fields. Also add async initialization of this.users in connectedCallback() using the channelUid attribute.
The key handler for the double-press of 'g' now scrolls to the first message and triggers `loadExtra()` instead of scrolling to the last message and focusing the input. The escape key press no longer sets a flag but instead blurs the active element. The shift-G handler now always prevents default and updates layout regardless of input focus, with the focus condition commented out.
The replyMessage function now encloses the quoted message in triple backticks with a markdown language specifier, ensuring proper rendering of the quoted content in the chat input field.
Extract repeated DOM queries into module-level constants for chatInputField, messagesContainer, chatArea, channelUid, and username. Replace inline arrow functions in autoCompletions with direct references and a single-line clear implementation. Add a generic throttle utility and apply it to the refresh event listener to prevent rapid reloads on repeated server notifications.
Add `.message:has(+ .message.long-time)` selector to show time display when a message is followed by one with a date gap exceeding 20 minutes. Set `.time` opacity to 1 for both this case and the last message to override any inherited lower opacity. In the JavaScript, track `previousDate` alongside `previousUser` and add/remove `long-time` class based on a 20-minute threshold between consecutive messages' `created_at` timestamps.
The bug was in `embed_youtube` function in `src/snek/system/template.py` where the variable `start_time` was being sliced instead of the loop variable `t` when stripping trailing 's' characters from time values. This caused incorrect start time parsing for all entries after the first in multi-start queries.
The bug caused incorrect extraction of the numeric value from timestamp strings ending with 's' (e.g., "30s"). Previously, the code sliced `start_time[:-1]` (the list) instead of `t[:-1]` (the individual string element), resulting in the entire list being sliced rather than removing the trailing 's' character from each timestamp. This fix ensures proper parsing of YouTube embed start times by applying the slice operation to the correct variable.
Add `time_start` initialization and `uptime_seconds`/`uptime` properties to the Application class, including a `_format_uptime` helper that converts seconds into a human-readable string with days, hours, minutes, and seconds. Introduce a new `DumbTerminal` custom element in `dumb-term.js` with a retro terminal UI, command history navigation, and a shadow DOM-based layout. Additionally, reformat multiple JavaScript files (app.js, chat-input.js, chat-window.js, event-handler.js, fancy-button.js) to use consistent 2-space indentation and double quotes, and remove trailing whitespace from license comments.
Add `uptime_seconds` and `uptime` properties to the Application class in `src/snek/app.py`, tracking server runtime from `datetime.now()` on startup. Introduce a new `DumbTerminal` custom element in `src/snek/static/dumb-term.js` providing a retro terminal UI with command history. Reformat multiple JS files (app.js, chat-input.js, chat-window.js, event-handler.js, fancy-button.js) from 4-space to 2-space indentation and unify quote style to double quotes.
- Extend triggerGlow method in MessageList to accept optional color parameter and call app.startField.glowColor(color)
- Add _scheduled list to RPCView session class to track pending scheduled calls
- Refactor _schedule method to use self.user_uid instead of passed uid parameter and manage _scheduled list
- Add scheduled list tracking in RPCView.get() and conditionally skip deployment refresh if scheduled tasks exist
- Add `time_start` and `uptime` property to Application class for server uptime tracking
- Implement starfield CSS with day/night theme toggle and special star effects
- Add `reconnecting` event emission in Socket class and corresponding UI notification
- Introduce RPC session management with `_session_ensure`, `session_get`, and `session_set` methods
- Add `_schedule` helper in RPCView for delayed message delivery to users
- Wire up `refresh`, `deployed`, and `starfield.render_word` WebSocket events in web template
- Changed `connectedCallback` to set `this.user = null` immediately and fetch user asynchronously via `.then()` instead of blocking with `await`
- Added `setTimeout` with 1000ms delay to call `this.focus()` after message send handler registration
- Updated `scrollToBottom` in message-list to use `scrollIntoView` on `.message-list-bottom` element with a 200ms retry timeout instead of direct `scrollTop` assignment
The scrollToBottom method previously used scrollIntoView on the
message-list-bottom element, which caused inconsistent scrolling behavior
when new messages were added. Reverting to the direct scrollTop assignment
ensures the container scrolls reliably to the full content height.
Standardize all JavaScript files in src/snek/static/ from 4-space to 2-space indentation and convert single quotes to double quotes for string literals, ensuring consistent formatting across app.js, chat-input.js, chat-window.js, event-handler.js, fancy-button.js, and file-manager.js. Also add new DumbTerminal custom element class in dumb-term.js.
Add logic to detect time gaps between consecutive messages from the same user. When the difference between two messages' timestamps exceeds 20 minutes, the later message receives a `long-time` CSS class, causing its timestamp to be displayed persistently alongside the existing `switch-user` rule. The CSS selector `.message:has(+ .message.long-time)` ensures the preceding message's time remains visible when followed by a long-delayed reply.
- Replace avatar opacity-only hiding with max-height:0 and overflow:hidden for collapsed state
- Remove .time from the display:none rule shared with .author
- Add CSS rule to fade .time to opacity:0 when message row is not hovered, focused, or active
- Restore max-height:unset on .avatar when message is hovered to ensure smooth reveal
Add max-height:0 with overflow:hidden to .avatar in collapsed state, and set
max-height:unset on hover to prevent layout shift. Replace .time display:none
with conditional opacity:0 when message is not hovered, focused, or active,
keeping .author display:none unchanged.
- Refactor embed_youtube to use urlparse and parse_qs for robust YouTube link detection
across multiple hostnames (youtu.be, youtube.com, youtube-nocookie.com) and path variants
- Add missing file existence check in ChannelAttachmentView to return 404 when attachment
file is not found on disk
- Improve image saving in attachment view with quality=100, optimize=True, save_all=True
- Set Content-Type header from original format in attachment download response
Implement a new avatar generation system using the AnimalAvatarGenerator class that produces SVG animal avatars based on query parameters. Add an in-memory cache in AvatarView to store generated avatars per user ID, and introduce a getStarPosition helper in sandbox.html that classifies star positions into cardinal directions and center/edge regions.
The glowCSSVariable lighten factor was reduced from 80 to 10 to produce a more subtle visual effect. The updateStarColorDelayed function is now exposed on the app object as app.updateStarColor for external access. The unused StarField class and its instantiation have been commented out to clean up dead code.
Introduce a new StarField JavaScript class that generates animated star elements with customizable count, size range, speed, and color. Instantiate it on document body with default settings to enhance the sandbox visual experience.
Add four new container management views (index, create, update, delete) with corresponding router entries in the application. Refactor sandbox star field to use ES modules and CSS custom properties for dynamic star color. Extend the typing indicator RPC method to accept an optional color parameter, falling back to the user's stored color, and include it in the broadcast payload. Update the chat input component to fetch the current user asynchronously and pass their color when triggering typing events.
Introduce a new Container entity with fields for id, name, status, resources, user_uid, path, and readonly. Register ContainerMapper in the mapper registry, Container model in the model registry, and ContainerService in the service registry. Implement create, get, update, and delete methods in the service layer. Add Jinja2 templates for listing, creating, updating, and deleting containers under settings/containers/, including form styling and Font Awesome icons.
- Change default theme from dark2 to light1 in gitlog.py
- Update server port from 8000 to 8481
- Remove background-color styling from diff line highlights in gitlog.py
- Switch index.html body background from #111 to #000
- Add link to /static/sandbox.css stylesheet
- Inject JavaScript generating 200 animated star elements with randomized position, size, and flicker timing
Introduce a twinkling starfield effect on the app page by creating a new `sandbox.css` stylesheet with `.star` positioning and `twinkle` keyframes, and a `sandbox.html` template that generates 200 randomized star elements with varying size, animation duration, and delay. Wire both into `app.html` by linking the stylesheet and including the template before the closing body tag.
Add a new sandbox.css stylesheet defining absolute-positioned star elements with a twinkle keyframe animation, and a sandbox.html template that generates 200 randomized stars (position, size, animation duration/delay) via JavaScript. Include both files in app.html via link and include directive.
- Append `&format=webp` to the image source URL in `enrich_image_rendering` function
- Restore webp format query parameter that was previously removed, enabling browser fallback to webp when supported
- Replace fragile string splitting with urlparse/parse_qs for robust YouTube URL parsing
- Support youtu.be, youtube-nocookie.com, and /embed paths alongside /watch
- Properly extract start time (t=) parameter and append to embed src
- Add early 404 return in ChannelAttachmentView when channel_attachment is None
- Set Content-Type header from original format in attachment download response
Refactor the chat input area in web.html to use the new ChatInputElement custom component instead of inline HTML. Move autocomplete handler from a function-based getInputField() to directly attaching to the chat-input element's autoCompletions property. Remove the legacy initInputField function and its associated keydown/input event listeners, consolidating all input behavior into the component. Add the chat-input.js module script to app.html to ensure the custom element is registered globally.
Complete redesign of the index.html template from a minimal stub to a polished landing page. Adds a gradient hero section with call-to-action buttons, a responsive feature card grid, and a cohesive dark color scheme with hover effects. Introduces CSS reset, container layout, and link styling for a modern, production-ready appearance.
Add an `enabled` attribute initialized to `False` in the Cache constructor, and guard clauses at the start of `get`, `set`, and `delete` methods that return immediately when the cache is disabled, allowing runtime toggling of caching behavior without modifying callers.
The threshold for considering a user as recently active in the channel presence
yield logic was raised from 20 seconds to 180 seconds, reducing the frequency
of presence updates and accommodating longer intervals between user pings.
- Introduce `start_user_availability_service` method in Application class that creates an asyncio task for periodic user availability updates
- Implement `user_availability_service` coroutine in SocketService that iterates connected sockets every 60 seconds, updating each user's `last_ping` timestamp and persisting changes via the user service
- Add logging for socket addition and user availability service lifecycle events
- Reorder startup hooks to ensure user availability service starts before SSH server and database preparation
Insert a new command object for "/live" with description "Toggle live typing mode" into the commands array in the help dialog template, placed after the existing "/img-gen" entry.
Extract the online users dialog from `online.html` into a dedicated `dialog_online.html` template, create a new `dialog_help.html` template with a custom `help-command-list` web component, and add global `<dialog>` styling in `base.css` with centered positioning, backdrop blur, and fade/scale animations. Update `web.html` to include both dialog templates, wire the `/help` autocomplete command to `showHelp()`, and rename the `/online` handler from `showOnlineUsers()` to `showOnline()`.
- Add `get_seconds_since_last_update` method to `ChannelMessageModel` for age checking
- Reject edits on messages older than 3 seconds in `updateMessageText` RPC handler
- Broadcast full message record (including html) instead of just text in socket event
- Update frontend `updateMessageText` to parse and set innerHTML from received html field
- Fix CSS selector for chat message images to use descendant combinator
- Add 3-second auto-clear timeout for liveType input field
- Prevent liveType submission on trailing newline or space
- Hide empty messages in channel-message event by setting display none
Implement MessageList custom element with real-time message text updates and typing glow animation, add UserList component with relative time formatting, introduce LoadBalancer class for backend process management, create DatasetWebSocketView for key-value sync over WebSocket, override save method in ChannelMessageService to render HTML templates, and return channel_message object from ChatService instead of boolean.
Add urllib.parse import and encode the relative_url field of each attachment record before returning it in the JSON response for file uploads, ensuring special characters in URLs are properly escaped for client consumption.
Add `&height=240` query parameter alongside existing `?width=240` when
processing image source URLs in the BeautifulSoup-based template rendering
function, ensuring both dimensions are explicitly set for responsive image
generation.
The previous implementation used `list.append()` on a string object, which is invalid since strings do not have an append method. Changed to use the `+=` operator to properly concatenate the "?width=420" query parameter to the image source URL within the BeautifulSoup HTML processing pipeline.
The change modifies the img tag's src attribute within the picture template generated by the enrich_image_rendering function, adding a "?width=420" query parameter to the image URL while leaving the title and alt attributes unchanged. This ensures images rendered through this template are served at a constrained width of 420 pixels.
The change adds `?width=420` to the title attribute of embedded image tags, ensuring that when images are rendered, the title string includes a width hint for responsive or constrained display behavior. This affects the `embed_image` function in `src/snek/system/template.py` where image elements with extensions like `.jpg`, `.png`, `.gif`, `.webp`, `.heic` are processed.
Implement a full-screen image preview overlay triggered by clicking any <img> element inside the messages container. The overlay uses a fixed dark background (rgba(0,0,0,0.9)) and centers the image at up to 90% viewport dimensions. Clicking the overlay dismisses it by removing the element from the DOM.
The diff removes the `time.time()` calls and the `print` statement that logged the duration of each task execution inside the worker loop of `Application`. This eliminates unnecessary overhead and console noise from the hot path.
Introduce a new research module implementing a custom dataset wrapper with raw WebSocket client connectivity, a SQL schema for chat application tables (users, channels, messages, notifications), and a sync service that propagates database queries between nodes via WebSocket. Includes a performance test harness comparing the wrapper against direct dataset usage.
Add Pillow and pillow-heif dependencies to pyproject.toml, implement image format conversion and dimension resizing in ChannelAttachmentView.get() using query parameters 'format', 'width', and 'height', and update markdown rendering to support strikethrough, spoiler, and URL plugins. Also fix uvloop import to gracefully fall back on Windows, clean up unused imports in sssh.py, and change upload markdown syntax from image to link format in web template.
Introduce a new SSH server module (snek/sssh.py) that provides SFTP access using asyncssh. The server authenticates users synchronously via the existing password validation logic, chroots each session to the user's dedicated drive directory, and is started as an application startup hook on 0.0.0.0:2042. Refactor security helpers (hash, verify) to expose synchronous variants for use in the SSH authentication flow without async context.
Implement real-time typing indicator feature across frontend and backend. Add `set_typing` RPC method in `RPCView` that broadcasts typing events with user details to a fixed channel. Introduce `typeLock`, `typeListener`, and `set_typing` method in `App` class to periodically send typing status. Update `Socket` to emit custom events from incoming data. Add CSS `glow` keyframe animation and apply it to user avatars in `web.html` when `set_typing` event is received.
Introduce a new DBService that manages per-user SQLite databases stored under each user's home folder using the `dataset` library. The service provides async methods for insert, update, upsert, find, get, delete, and raw query operations. Corresponding RPC handler methods (db_insert, db_update, db_delete, db_get, db_find, db_upsert, db_query) are added to the RPCView class, each requiring an authenticated session via _require_login. The service is registered in the get_services factory and injected into the services dictionary.
Add `humanize` to project dependencies for human-readable file sizes and timestamps. Implement `BareRepoNavigator` class in repository view to support browsing bare git repositories by branch, commit, and path. Update route parameters from `repo_name`/`rel_path` to `repository`/`path` for consistency with other views.
Revert commit 17c6124a57 which minified all Python source files, restoring original multi-line formatting, expanded variable names, and proper indentation in __main__.py, app.py, docs/app.py, dump.py, form/login.py, and form/register.py
Introduce a <file-browser> web component with pagination, breadcrumb navigation, and grid-based file tiles. Implement RepositoryView that serves repository contents by walking the git commit tree, sorting entries by type and name, and rendering them via the repository.html template with folder/file icons and size display.
- Register RepositoryView and DriveView routes for repository and drive browsing
- Add `is_available` boolean field to DriveItemModel with default True
- Implement `get_by_username` method in UserService for username-based queries
- Comment out repository deletion logic in GitApplication
- Include file-manager.js script and Font Awesome CDN stylesheet in app template
- Fix repository URL paths in settings template to use correct attribute access
- Refactor DriveView with full directory listing and file serving capabilities
- Pass user object to repositories index template for template rendering
Migrate the command-line interface from argparse to click, introducing a `cli` group with `serve` and `shell` subcommands. The `serve` command retains port, host, and db_path options with defaults, while the new `shell` command launches an IPython shell with the application context. Update Makefile targets to use the new `.venv/bin/snek` binary and add a `serve` alias for `run`.
The Application class now passes a 5GB client_max_size limit to its parent constructor, while the GitApplication class has its existing limit raised from 100MB to 5GB, enabling support for larger file uploads and repository operations.
Replace the stub that always returned a fixed user and path with full HTTP Basic authentication logic. The method now extracts and base64-decodes the Authorization header, splits credentials, authenticates the user via the parent services layer, retrieves the user-specific repository path, and returns the username and path only on successful authentication.
- Add uvloop to project dependencies in pyproject.toml for async event loop optimization
- Fix repository name extraction by replacing lstrip('git/') with slice [4:] to avoid stripping leading characters incorrectly
- Normalize string quotes from single to double for repo_dir path construction
- Add trailing whitespace to return statement in get_repository_path helper
Add a new GitApplication subapp mounted at /git with smart HTTP protocol support for repository operations. Include gitpython dependency in pyproject.toml, fix recursive call in RepositoryService.exists by delegating to super(), and change RepositoryService.init to return boolean success instead of printing output. Also modify UserService.get_repository_path to always return a path object without checking existence.
Implement full repository management feature including RepositoryModel with user_uid, name, and is_private fields, RepositoryMapper for database persistence, and RepositoryService with exists, init, and create methods that spawn bare git repos via asyncio subprocess. Add four new settings views (index, create, update, delete) with corresponding templates and route registrations in Application. Register repository mapper, model, and service in their respective __init__ factories. Extend UserService with get_repository_path helper. Switch event loop policy to uvloop in __main__.py for improved async performance.
The diff shows a one-line change in `src/snek/app.py` within the `Application` class. In the static path resolution logic, the code previously appended `user_template_path` to the paths list when a user-specific static path existed. This was a bug — the intended variable is `user_static_path`, which holds the actual static directory path returned by `get_static_path`. The fix ensures that the correct static path is used for user-specific static file serving, preventing potential 404 errors or incorrect file resolution when users have custom static directories.
The session uid retrieval in the Application class was incorrectly referencing
self.request.session instead of the local request variable, causing potential
AttributeError when accessing user static paths.
Add `static_path` attribute to Application and introduce `static_handler` method that resolves static files from user-specific directories, admin directories, and the default static path. Implement `get_static_path` in UserService to return user-specific static directory path if it exists.
The tmux terminal multiplexer package is appended to the existing RUN apt install command, ensuring it is available in the Ubuntu-based Docker image alongside other development and utility tools.
The focus call for the input field after sending a message is now wrapped in a setTimeout with a 500ms delay. This prevents the focus from being applied before the DOM has fully updated after the new message is rendered, ensuring the cursor reliably lands in the input field for the next keystroke.
When a message is sent via Enter key, the input field now automatically regains focus, allowing the user to continue typing without manually clicking back into the text area. This improves the upload and messaging flow by eliminating an extra interaction step.
Implement a keydown listener that tracks Escape key state and counts subsequent 'G' presses. When Escape is held and 'G' is pressed twice within 300ms, the view scrolls to the last message in the messages container. Also handle Shift+G to trigger layout update via existing updateLayout function.
Implement file upload via clipboard paste events in the web textarea input, reading non-text clipboard items as blobs and feeding them into the existing upload-button component. Also add drag-and-drop event listeners to handle dropped files, enabling direct file uploads without manual file selection.
- Introduce UIDNS class and uid() function in security.py for deterministic UUID5 generation
- Refactor hash() and verify() with type hints, docstrings, and string-based salt handling
- Replace global UPLOAD_DIR constant in upload.py with dynamic per-user upload folder derived from session uid
- Update file path handling to use user-specific upload directory instead of hardcoded ./drive path
Implement clipboard paste event listener in initInputField that reads image files from clipboard and triggers upload via the upload-button component. Add drag-and-drop file drop support to the textarea input field for improved user experience.
Introduce a new `uid` function that generates UUID5 values from a namespace and optional input string, along with a `UIDNS` helper class. Update `hash` and `verify` functions with explicit type hints, docstrings, and change `DEFAULT_SALT` from bytes to string for consistency.
The diff adds two new sections to pyproject.toml: [tool.setuptools.packages.find] with where = ["src"] and [tool.setuptools.package-data] with "*" = ["*.*"] to ensure proper package discovery and data file inclusion during build.
Add argparse-based CLI with --port, --host, and --db_path options to __main__.py,
replacing hardcoded web.run_app call with a main() function. Register snek console
script in pyproject.toml pointing to snek.__main__:main.
Refactor the subscription body validation in `src/snek/view/push.py` to use `all()` with a list comprehension instead of a multi-line `and` chain, improving code clarity. Also normalize string quotes from single to double for `body["endpoint"]` and reformat the `requests.post` call to span multiple lines for consistency with project style.
The `position:fixed` declaration was removed from the header element within the
max-width 768px media query, allowing the header to scroll naturally with content
on smaller screens instead of remaining fixed at the top.
Add a "Navigation" section to the user sidebar template in `src/snek/templates/user.html`, including a "Back" link that triggers `window.history.back()` for improved user navigation flow.
- In message.html template, moved the avatar `<img>` inside an `<a>` tag to make the user avatar clickable, linking to the user's profile page.
- In user.py view, changed `get()` to extract `user_uid` from request match_info and fetch the user record via `self.services.user.get(uid=user_uid)` instead of relying on `self.request['user']`, passing the uid to the template context.
Replace the manual fetch-and-save pattern in the `set` method of `UserPropertyService` with a direct `upsert` call on the underlying mapper, using `user_uid` and `name` as the conflict keys. This eliminates the round-trip to check for an existing property before inserting or updating, and removes the dependency on the base service's `get`, `new`, and `save` methods for this operation.
Implement a new user view and template that displays user profiles with a sidebar containing profile and DM links, gist listing placeholder, and renders the user's profile content as markdown. The UserView class fetches the profile property from the user_property service and passes both the user record and profile content to the template.
- Import and register UserView at /user/{user}.html in app router
- Wrap avatar img in message template with anchor to user profile page
- Remove monaco editor and header text from settings template, replace with simple logo block
- Changed UserPropertyService.set() to call super().get() instead of self.get() to avoid recursion
- Fixed UserPropertyService.get() to access "value" key from dict and added exception logging
- Updated settings/profile.html to include a proper POST form with profile textarea and submit button
- Added redirect to /web.html for authenticated users in IndexView.get()
- Refactored SettingsProfileView to handle POST form data directly, saving nick and profile properties
Refactor `create_encrypted_payload` to accept endpoint parameter and return a dictionary containing pre-built HTTP headers and encrypted payload data, eliminating manual header construction in the PushView caller. Remove unused `public_key_base64` certificate retrieval and inline header generation logic.
Introduce a new configuration file defining the assistant's identity, capabilities, and operational guidelines. The file establishes core facts such as the assistant's name (`R`), creator (`retoor`), tool usage rules (e.g., `linux_terminal_execute_interactive` for interactive editors, `linux_terminal_execute` for `apt` commands), and coding practices (no comments, no `sudo`, defensive coding). It also outlines a 11-step work procedure emphasizing file investigation, complete feature implementation, Makefile generation, and aggressive file management with overwrite checks.
Implement a new StatsView class under src/snek/view/stats.py that exposes a GET handler. The handler retrieves statistics from the application cache, serializes them with total count and indented JSON formatting, and returns the result with application/json content type.
Introduce per-key hit/miss/delete counters in the Cache class, update them on every get/set/delete operation, and expose the aggregated stats through a new StatsView at /stats.json. Also add a __repr__ method to the Validator model for easier debugging.
Replace commented-out push handler with a full async `registerServiceWorker` function that fetches the VAPID public key, subscribes the push manager, and POSTs the subscription object to `/push.json`. Remove unused helper functions (`arrayBufferToBase64`, `requestNotificationPermission`, `subscribeUser`) and top-level fetch calls. Convert notification key file paths from string constants to `pathlib.Path` objects, replacing `os.path.isfile` checks with `.exists()` and `open`/`write` calls with `.read_bytes()`/`.write_bytes()`. Update the notification test payload to use relative icon and URL paths. Fix the app.html onclick handler to call the renamed `registerNotificationsServiceWorker()` function.
- Extend service-worker push event to parse and display notification body, icon, and title from push data payload
- Add AESGCM and HKDF imports to notification module for future encryption support
- Refactor notification key file path constants to use double quotes for consistency
- Update push view to include notification body in subscription response and validate request fields
- Add async generator `get_user_uids` to ChannelMemberService for direct user_uid queries
- Fix `broadcast` in SocketService to use new method instead of full channel_member find
- Remove early return in `subscribe` method to allow actual subscription logic
- Improve error logging in RPC view and socket send/close methods
Remove HTML comment markers around the "Terminals" heading and link in sidebar_channels.html, and close the comment block before the channels section so both terminal and channel lists are displayed.
Uncommented the `v=` parameter parsing branch in `embed_youtube` to correctly extract video IDs from YouTube URLs containing query strings, replacing the previous fallback that only handled path-based IDs. Also removed the trailing semicolon from the `style` attribute in the iframe embed template for consistency with HTML attribute formatting conventions.
The diff corrects a logical bug in `embed_youtube` within `src/snek/system/template.py` where the `and` operator had higher precedence than `or`, causing the condition to match links starting with "https://www.you" only when `v=` was present, but also matching any link with `si=` regardless of domain. By wrapping the two `or` conditions in parentheses, the fix ensures both `v=` and `si=` parameters are only checked against youtube links.
Replace naive split-on-slash video name extraction with proper query parameter parsing for v=, si=, and t= parameters. Handle cases where href contains si= without v= by constructing embed URL with ?v= prefix from the last path segment. Remove commented-out legacy parsing code and adjust iframe src construction accordingly.
The diff removes an incorrect `or` condition that checked for "si=" in the href,
which caused false positives and broke the video name extraction logic for
standard YouTube URLs containing query parameters.
Simplify video ID extraction by taking the last path segment from the href instead of parsing query parameters for v=, si=, and t=, removing the broken multi-parameter concatenation logic.
The diff modifies `embed_youtube` in `src/snek/system/template.py` to handle YouTube share URLs that use `si=` instead of `v=`, and changes the video ID extraction to pass the entire query string (including `si=`) to the embed iframe src. A `<br />` tag is also prepended to the embed template for spacing.
- Added aiohttp middleware `debug_middleware` that logs request method, path, and headers
- Injected `debug_middleware` into the application's middleware stack via `middlewares` parameter
- Set `self.relative_url` attribute to "/webdav" and added debug print of router
- Uncommented and enabled LOCK and UNLOCK route registrations for filename-based paths
Implement a new service class `UserPropertyService` in `src/snek/service/user_property.py` that provides CRUD operations for user properties. The `set` method creates or updates a property with JSON-serialized values, `get` retrieves and deserializes a property by user UID and name, and `search` performs case-insensitive partial matching on property names with an empty query guard returning an empty list.
Introduce UserPropertyService and wire it into service registry; add get_property, has_property, set_property methods to UserModel for dynamic user attributes. Move ThreadPoolExecutor initialization from module-level to Application.prepare_asyncio startup hook, ensuring executor is bound to app instance. Include IPython in dependencies, fix missing newline at end of app.js, adjust sidebar logo font-size from 12px to 14px, and add header_text block to search_user and threads templates.
- Export registerServiceWorker function in push.js and add debug logging for key data
- Add notificationclose event listener and fetch cache-first strategy in service-worker.js
- Add notification button to app.html template calling registerServiceWorker
- Fix public key encoding from base64 to urlsafe_b64encode in push.py for chrome compatibility
The manifest.json previously referenced a single icon file (snek1.png) for both 192x192 and 512x512 sizes, causing browser instability warnings due to mismatched dimensions. This commit adds properly sized snek192.png and snek512.png images and updates the manifest to point each icon entry to its corresponding size-specific asset, resolving the Firefox Android manifest validation issue.
Introduce an authentication middleware that attaches the user object to requests based on session data, register the UserPropertyMapper and UserPropertyModel in the service layer, replace the monolithic SettingsView with dedicated SettingsIndexView and SettingsProfileView, update the settings template to remove inline Monaco editor, delete the old sidebar_settings partial, and wire the settings gear icon in the app navigation to /settings/index.html.
- Added `width:100%` to anchor element in base.css to ensure full-width layout
- Removed `float:right` from nav element in app.html to prevent layout overflow issues
Adjust the .container width in style.css to 500px, improving layout
compatibility on narrower phone screens while preserving padding and
border-radius.
Add display:flex with justify-content:space-between and align-items:center to the footer element, along with 10px vertical and 20px horizontal padding, to create a responsive two-column footer layout for the chat application's base stylesheet.
The diff removes the `<chat-window>` element that was hidden with `display:none` from the web.html template, likely because it is no longer needed or has been replaced by a different component.
Add responsive CSS media query for max-width 600px making header position sticky with block display for logo, and make chat-input footer sticky. Change chat-input container from div to footer element in web.html template for semantic HTML structure.
Add a new SettingsView at /settings.html with a sidebar navigation for profile, notifications, and privacy sections. The settings page includes a Monaco code editor for profile description editing. Also fix the WebDAV propfind handler to only include contenttype and getcontentlength properties for files, preventing errors when these properties are requested on directories.
The `folder.mkdir(parents=True, exist_ok=True)` call in `UserService.get_home_folder` could raise an unhandled exception (e.g., permission errors, filesystem issues) when creating the user's home directory. Wrapped the call in a try-except block to silently ignore failures, preventing crashes during user operations while maintaining existing behavior for already-existing folders.
Extract the XML response node creation logic from the PROPFIND handler into a new async create_node method on WebdavApplication, reducing handler complexity and enabling reuse for future multi-node responses. The method handles href path normalization, resource type detection, quota reporting, and metadata population including creation date, last modified, content type, and lock discovery.
The user home folder path is updated from `./drive/{user_uid}` to `./home/{user_uid}` in the `get_home_folder` method of `UserService`, aligning with the new WebDAV storage layout.
The session-based authentication block in `WebdavApplication.authenticate` (lines 48-56) has been fully commented out, including the `session` lookup, `user` retrieval via `services.user.get`, and `home` folder assignment. The method now unconditionally proceeds to parse the `Authorization` header for Basic credentials, effectively switching the WebDAV endpoint from session-based to HTTP Basic authentication.
Implement a complete WebDAV application module under src/snek/ including:
- WebdavApplication class with route handlers for OPTIONS, GET, PUT, DELETE, MKCOL, MOVE, COPY, PROPFIND, PROPPATCH, LOCK, UNLOCK methods
- Authentication integration via session-based user lookup and home folder resolution
- Lock management infrastructure with in-memory locks dictionary
- XML parsing support using lxml for PROPFIND/PROPPATCH request bodies
- Async file I/O operations via aiofiles for GET/PUT handlers
- MIME type detection and datetime formatting for resource metadata
- Base64 encoding support for lock token generation
- Logging configuration at DEBUG level for development
Add WebdavApplication to the app router and include lxml in project dependencies to support WebDAV protocol functionality. Also perform minor code style cleanups across multiple modules (quote normalization, import reordering, formatting adjustments) without behavioral changes.
Add DockerfileUbuntu that pre-installs r binary and all required system dependencies, removing the need for per-container apt installs and binary copy from .bashrc. Update terminal view to use the new snek_ubuntu image instead of ubuntu:latest. Fix terminal session buffer check to use self.history instead of self.buffer. Add docker build target to Makefile.
The diff shows a single-character change in `src/snek/service/chat.py` where `async self.app.create_task(...)` was corrected to `await self.app.create_task(...)`, fixing a syntax error that would have prevented the notification task from being properly awaited.
The notification task creation was placed before the socket broadcast, causing
potential ordering issues where notifications could be sent before the message
was broadcasted to active channel members. This commit reorders the operations
to ensure the socket broadcast completes before the notification task is created.
The diff modifies two lines in `src/snek/service/chat.py` within the `ChatService` class. The first change removes the `await` keyword from the notification channel message creation, wrapping it instead with `async self.app.create_task()`. The second change removes the outer `await self.app.create_task()` wrapper from the socket broadcast call, making it a direct synchronous call. These adjustments alter the concurrency model for both notification delivery and socket broadcasting operations.
Add time measurement around task execution in the worker loop of Application class, printing elapsed seconds for each completed task. Also call self.tasks.task_done() after successful task completion to properly signal task queue completion tracking.
Add explicit `self.db.begin()` before task execution and `self.db.commit()` after completion in the `task_runner` method of `Application` class. Previously tasks ran without explicit transaction boundaries, relying on implicit database behavior. This change ensures each task is executed within a well-defined transaction scope, providing proper isolation and atomicity guarantees for database operations performed during task processing.
The diff removes explicit `self.app.db.begin()` and `self.app.db.commit()` calls from the RPC message processing loop in `src/snek/view/rpc.py`, suggesting transaction management is now handled elsewhere or implicitly.
Replace the list-based task storage and polling loop with an asyncio.Queue for proper async task scheduling, eliminating the 0.1s sleep and IndexError handling. Also change SocketService.broadcast to return boolean instead of integer count, and add explicit exception logging in task_runner.
Introduce a lightweight in-process task queue on the Application class with `create_task` and `task_runner` methods, enabling deferred execution of background operations. Refactor `ChatService.send` to enqueue the socket broadcast as a task instead of awaiting it directly, and move the `last_message_on` channel update before the broadcast to ensure timestamp consistency. The return value is simplified from `sent_to_count` to `True` since the broadcast result is no longer synchronously available.
The diff adds explicit database transaction boundaries around the notification creation loop in `NotificationService`. A `self.app.db.begin()` call is inserted before iterating over channel members, and a `self.app.db.commit()` is added after the loop completes. This ensures that all notification saves within the loop are executed atomically, preventing partial writes if an exception occurs during processing of individual channel members.
- Extend channel sidebar template to display per-channel unread message count and color-coded names based on the last message author's color
- Add `color` and `new_count` fields to channel objects in both server-rendered sidebar and RPC `getChannels` endpoint
- Optimize `ChannelModel.get_last_message` to fetch only `uid` first then retrieve full model, reducing data transfer
- Reset `new_count` to zero when a user views a channel in `WebView`
- Add debug prints for RPC responses and exceptions in `RPCView`
The drive item URL was incorrectly referencing `drive_item.extension` which does not exist on the record object, causing AttributeError when building item URLs. Changed to use `item.extension` from the async iterated DriveItem object to properly construct the download path.
In both the single drive retrieval and the user drives listing endpoints, each drive item record now includes a computed 'url' field constructed from the item's uid and extension, enabling clients to directly access the binary file at '/drive.bin/<uid>.<ext>'.
- Register DriveView at /drive.json and /drive/{drive}.json endpoints in app router
- Add DriveModel.name field and async items property yielding drive items
- Add DriveItemModel.extension and mime_type properties using mimetypes
- Implement DriveService.get_drive_name_by_extension and get_drive_by_extension for automatic drive categorization
- Store file extension in DriveItemService during item creation
- Refactor TerminalSession to use async subprocess with lazy process initialization and multi-socket output buffering
- Move database preparation and service initialization to async startup hook with await on drive.prepare_all
Introduce a per-session byte buffer that accumulates terminal output up to 10KB, allowing newly connected websocket clients to receive the last line of buffered output before the live stream begins. Also improve error handling by terminating the subprocess and closing all active websockets on read failure, and adjust terminal HTML layout to use 90% width with hidden scrollbar for a cleaner full-height display.
The chmod flag for /usr/local/bin/r was incorrectly set to -x (remove execute),
preventing the binary from being run. Changed to +x to grant execute permission,
allowing the r binary to be invoked as intended.
Add channel subscription loading in render_template to populate sidebar with
private DM channels showing other user's nick, sort channels by last_message_on
descending, and inject user object into template context for authenticated sessions.
Add a "Terminals" heading with an Ubuntu link above the channels list in the sidebar template, and wrap the channels section in a conditional check for non-empty channels. Replace the placeholder "Reboot" text in terminal.html with an include of the sidebar_channels template to render the full sidebar.
Add a web-based terminal accessible at /terminal, backed by per-user ephemeral Docker containers. Each user gets an isolated Ubuntu container with their drive directory mounted at /root, memory/cpu limits, and a prepared .bashrc/.profile. The terminal uses xterm.js over a WebSocket binary transport for full ANSI support. Also migrate the base Docker image from Alpine to Debian bookworm (python:3.14.0a6-bookworm), remove wkhtmltopdf dependencies, and fix the compose volume path from /media/storage/snek/molodetz.nl/drive to /media/storage/snek.molodetz.nl/drive.
- Add docker package to Dockerfile for container management capabilities
- Mount docker socket and storage volumes in compose.yml with privileged mode
- Register TerminalView and TerminalSocketView routes in app router for web terminal access
Replace four separate session key assignments with a single dict update in both RegisterView and RegisterFormView, and fix missing trailing newline in login.py.
Convert dump_public_channels to async function with asyncio.run entry point, add fix_message helper to transform message records with user lookup, filter channels by tag='public', order messages by created_at, and write formatted JSON output to dump.json file instead of stdout. Also suppress verbose cache debug prints in Cache class.
Introduce a new `dump` make target that invokes `snek.dump` module, and implement the `dump_public_channels` function which iterates over non-private, listed channels, collects their metadata and associated messages, and outputs the result as formatted JSON to stdout.
Create a new polyfill script at src/snek/static/polyfills/Promise.withResolvers.js that provides a fallback implementation of Promise.withResolvers for environments lacking native support. Include the polyfill as a module script in both app.html and base.html templates, ensuring it is loaded before app.js to guarantee availability at runtime.
The polyfill provides a static method on Promise that returns an object with resolve and reject functions exposed, enabling external resolution of a promise. It is loaded as a module script in both app.html and base.html to ensure compatibility before app.js runs.
- Split header padding into individual properties (top, left, right) in base.css
- Reduce logo font size from 1.5em to 1.2em in base.css
- Hide static "Snek" logo div with inline display:none in app.html
- Add new logo div with block header_text template block in app.html
- Move chat header h2 from web.html into the new header_text block in app.html
Introduce initial push notification support by adding a new `notification.py` module for VAPID key pair generation (EC P-256), a `PushView` endpoint serving the public key, and a `push.js` client that fetches the key and subscribes the service worker with `userVisibleOnly` flag. The service worker push listener is refactored to check notification permission and handle click events. The `app.html` template now includes the push script as a module, and the application startup calls `get_notifications()` to ensure keys exist. Note: FCM-based push on Chrome/Opera remains non-functional due to unresolved compatibility issues.
Inject the umami analytics script with website ID d127c3e4-dc70-4041-a1c8-bcc32c2492ea into app.html, base.html, and index.html templates for visitor tracking
Replace the page background (#f4f4f4) and upload button background (#f05a28) with solid black (#000000), and invert the progress bar overlay from semi-transparent white to the previously used orange (#f05a28) for visual contrast.
Convert all JavaScript classes in app.js and schedule.js to ES module exports, add import statements in inline scripts, and update all script tags across templates to use type="module" attribute. This enables proper module scoping and dependency management for the frontend codebase.
Convert all JavaScript classes in app.js and schedule.js from implicit globals to ES module exports, add import statements in template scripts for the app instance, and mark all script tags with type="module" to enable module scoping. Also fix a potential null reference in web.html by using optional chaining on lastMessage.scrollIntoView and hoist the lastMessage variable to outer scope for proper closure behavior.
Create new shared.css with consistent input/textarea focus outline and placeholder transition effects. Import shared.css into both base.css and style.css to apply the unified input styling across all pages, replacing previously scattered per-page implementations.
Create a new shared.css file containing consistent focus ring and placeholder opacity transition rules for input and textarea elements. Import shared.css into both base.css and style.css to apply the updated input styling across all pages, replacing previously scattered or missing focus indicators.
The setInterval call for the updateTimes function was changed from 1 second to 30 seconds,
reducing the frequency of time display updates in the web interface to lower client-side
processing overhead and network requests.
The main content container in base.css now includes a padding-top of 10px to improve vertical spacing between the top edge and the content, ensuring consistent layout across pages.
Add `Content-Disposition: attachment` header to file download responses so browsers prompt for download instead of displaying inline. Replace user-provided filenames with UUID-based names on upload to prevent path traversal attacks and filename collisions.
Add `preloaded-structure` attribute to `<generic-form>` elements in both `login.html` and `register.html`, passing the serialized form JSON from the view. In `generic-form.js`, normalize CSS indentation from 10-space to 2-space tabs. This enables server-side form definition to be rendered immediately on page load without an initial JSON fetch, and the autofocus behavior targets the first input element in the preloaded structure.
Add `preloaded-structure` attribute to `<generic-form>` elements in both login and register templates, passing the serialized form JSON from the server. This enables instant form rendering without an additional fetch. Also implement autofocus logic in `generic-form.js` to focus the first input field on page load, improving UX for authentication flows.
Add conditional rendering in sidebar_channels.html to separate channels based on the `is_private` flag, displaying public channels under a "Channels" heading and private ones under a "Private" heading. In web.py, set `is_private` to true when the parent channel's tag equals "dm", enabling the template logic to distinguish direct message channels from regular ones.
Retrieve last_message_on from parent channel object for each subscribed channel and sort the channels list in descending order by this timestamp before rendering the template.
- Create back-form.css with grid layout for back button and form overlay
- Add border-radius: 20px to images, videos, iframes, and divs in message-content
- Add focus outline and placeholder transition to GenericField inputs
- Dispatch change event on Enter key in GenericField
- Add meta tags, title blocks, and head blocks to base.html, login.html, and register.html
- Wrap login/register forms in back-form div for consistent layout
Replace the plain list-based user search results with a rich thread-style card layout
featuring avatar, nick, color-coded author, and last ping timestamp. Update the backend
view to pass user record objects and current_user context to the template.
Add a new CSS file `back-form.css` defining a two-column grid layout for the back button and generic form, with the button positioned at (1,1) with 30px margins and z-index 1. Update both `login.html` and `register.html` to include the stylesheet in the head block and wrap the existing `fancy-button` and `generic-form` elements in a `div.back-form` container, replacing the previous standalone button placement.
Insert an empty Jinja2 block named 'head' inside the <head> element of base.html, enabling child templates to inject custom meta tags, styles, or scripts before the closing head tag.
Add a sort operation to the threads list before rendering the template, ordering threads by their last_message_on timestamp in descending order so the most recently active threads appear first.
Add six new meta elements to the HTML head in base.html: theme-color set to black,
application-name and description both set to "Snek chat by Molodetz", author set to
"Molodetz", keywords including "snek, chat, molodetz", and color-scheme set to dark.
Also insert a blank line after the title block for improved readability.
Set a default "Snek chat by Molodetz" title in base.html and override it with
"Login - Snek chat by Molodetz" and "Register - Snek chat by Molodetz" in their
respective templates, improving user-facing page identification.
Refactor search_user.html template to replace simple `<ul><li>` list with detailed thread-style cards showing avatar, nick, color, and last ping timestamp. Update search_user.py view to pass `current_user` context and unwrap user records from service response.
- Fix ChannelMemberModel.get_name() to check channel tag instead of uid for dm detection
- Add fallback to channel member label when channel name is empty
- Handle self-dm edge case in ChannelMemberService.get_dm() with null join
- Update threads template to use channel name and color instead of last message user data
- Add channel membership verification in WebView to return 404 for non-members
- Pass resolved channel member name to web template instead of raw channel label
Remove the empty chat header div with placeholder question mark heading, and add explicit opacity: 1 to both the author name and timestamp elements in thread message previews to ensure consistent visibility across all thread entries.
The diff removes the `class="avatar"` attribute from the `<div>` element wrapping the avatar image in the thread message template, while keeping the inline `background-color` and `color` styles. This change likely addresses a CSS specificity issue or removes an unintended class that was overriding or conflicting with other avatar styling rules in the thread list view.
The fix ensures that when `connect()` is called on an already connected socket, the promise resolves immediately with the socket instance rather than being deferred. Previously, the code only resolved immediately when not connecting, leaving connected sockets waiting indefinitely. The new logic checks `isConnected` first, then falls through to `isConnecting` before adding to the promise queue.
Implement ThreadsView handler that retrieves user channel memberships, fetches last message details (text, user nick, color) for each thread, and renders the threads.html template. The template displays a chat-style thread list with avatar, author, message preview, and relative timestamps using client-side time formatting.
The notification service's channel member query for sending notifications
now includes all members regardless of ban, mute, or deletion status,
allowing notifications to be delivered to previously filtered-out members.
Insert a debug print statement in the notification service's channel member update logic to log when a new notification count is incremented, aiding in development and troubleshooting of notification acceptance flow.
- Set model['new_count'] to 1337 when creating a new notification in NotificationService.create
- Remove trailing whitespace on empty line in channel message notification handler
The new_count field update logic is relocated from the ChatService (where it was applied to the channel_message document) into the NotificationService, where it now increments the new_count on the channel_member document instead. This ensures unread message counts are tracked per-member rather than per-channel, fixing the notification acceptance flow.
The diff shows two changes: in chat.py, new_count is now incremented on the channel_message document itself (with a fallback to 0 if missing) and saved before notification creation; in notification.py, the previous logic that incremented new_count on channel_member documents is removed, consolidating the count tracking onto the channel_message object.
Implement requestNotificationPermission and subscribeUser functions to handle browser notification permission requests and push subscription registration with the backend. Add a push event listener to display notifications from received push data. Comment out the existing install, activate, and notificationclick handlers to transition to the new push-based notification flow.
- In `set_link_target_blank`, extend `.strip(".")` to also strip trailing commas from href attributes
- In `UploadView`, change upload response link text from static "[url]" to dynamic filename variable
Add optional `last_message_on` string field to ChannelModel to track when the last message was sent. In ChatService.send(), validate channel existence before proceeding, set `last_message_on` to current timestamp after successful message delivery, and persist the updated channel record.
- Inserted a poetic tagline beneath the "Snek" heading to convey the project's ethos of being lean and optimized versus bloated competitors.
- Capitalized "OR" separator for visual consistency between Login and Register buttons.
- Removed three navigation links (Design choices, App preview, API docs) to simplify the landing page and focus on core registration actions.
The change modifies the call to `multiavatar.multiavatar()` in the avatar view handler, replacing the second argument from `None` to `True`. This enables triangulation for unique avatar identifiers, ensuring visually distinct avatars are generated instead of falling back to a default rendering path.
When the uid parameter equals the literal string "unique", the handler now generates a random UUID v4 to produce a distinct avatar instead of returning the deterministic multiavatar for the string "unique". This allows clients to request a fresh random avatar by passing "unique" as the uid.
Add a new `/avatar/{uid}.svg` route served by `AvatarView` that generates deterministic SVG avatars using the `multiavatar` library keyed on user UID. Register the view in the application router and update `message.html` to replace the plain initial-letter avatar with an `<img>` tag pointing to the new endpoint. Also add `multiavatar` to project dependencies in `pyproject.toml`, introduce `python` and `run` convenience targets in the Makefile, and set aggressive `Cache-Control` headers on both the avatar and upload responses to reduce server load.
Introduce a new sidebar_channels.html template that renders a channel list with unique IDs per channel. The accompanying ChannelSidebar JavaScript class provides methods to retrieve channel list items by UID, increment and display message counts, and update the DOM with the current count for each channel.
Refactor NotificationAudio.sounds from a positional array to a named object mapping event types ("message", "mention", "messageOtherChannel", "ping") to distinct audio files. Add three new notification WAV assets for mention, other-channel, and ping sounds. Update the channel-message event handler in web.html to use named sound keys, and introduce extractMentions() and isMentionForSomeoneElse() helpers to differentiate messages mentioning the current user from those mentioning other users, playing the appropriate notification sound for each case.
Add a new `isMention` function that checks if a chat message contains the current user's username, and play the new `mention1.wav` sound (index 1) when a mention is detected instead of the default beep. Also remove redundant dataset attributes from the message element creation in the channel-message event handler.
- Add unique index on username column in user table and composite indexes on channel_member and channel_message tables during app initialization
- Introduce .container CSS class with flex layout, padding, and nested styling for links and lists in base.css
- Change PWA manifest scope from /web.html to / to cover entire app
- Remove commented-out YouTube embed code from template.py and add .jpg to supported image extensions
- Rename "Chat Rooms" heading to "Channels" in app.html template
- Restructure search_user.html template with proper indentation and container class for form and results
- Strip sensitive fields (email, password, message, html) from RPC query results before returning them
Move the `Application` instance creation from inside the async `main()` function to module scope in `src/snek/app.py`, ensuring the app object is available at import time for gunicorn's worker initialization. In `compose.yml`, increase the gunicorn worker count from 1 to 4 to improve concurrent request handling under the aiohttp worker class.
The compose.yml service entrypoint is changed from the development python runner to
gunicorn with 4 workers using aiohttp worker class, binding on port 8081. The previous
python entrypoint is commented out for reference.
Insert a large multi-line ASCII art string as a new key in the emoji.EMOJI_DATA dictionary within the snek template module, expanding the emoji mapping with a decorative snake illustration.
- Introduce new profiler module in snek/system/profiler.py with middleware, handler, and context manager
- Register /profiler.html view in Application router to display profiling stats
- Wrap RPC WebSocket message handling with Profiler context manager for per-request profiling
- Remove asyncio event loop debug mode from main startup
- Comment out gunicorn entrypoint in compose.yml and use python -m snek.app instead
- Add asyncio import and create async main() function in app.py
- Enable asyncio event loop debug mode via loop.set_debug(True)
- Replace direct web.run_app call with asyncio.run(main()) for proper async context
The `display_override` array with value `["browser"]` was removed from the
`src/snek/static/manifest.json` file to prevent the browser from overriding
the intended display mode, ensuring the PWA launches in its configured
standalone or fullscreen mode as expected.
- Extend manifest.json with id, orientation, scope, theme/background colors, related_applications, screenshots, dir, lang, launch_path, display_override, and short_name for full PWA compliance
- Move manifest link element to head section and add missing rel attribute
- Comment out push.js script tag to disable legacy push functionality
- Refactor beforeinstallprompt handler: remove preventDefault call, show install button immediately on event, and clean up nested DOMContentLoaded listener
Changed the pagination offset parameter from 1 to 0 in the getMessages RPC call, allowing the chat to load messages that precede the current first message rather than skipping it. This enables seamless infinite scroll behavior when the user scrolls up in the message history.
The `isScrolledPastHalf()` check was previously commented out, causing the chat to always load extra messages regardless of scroll position. This change re-enables the guard and adjusts the threshold from 1/4 to 1/2 of the scrollable height, ensuring new messages are only fetched when the user has scrolled past the midpoint of the current content.
The conditional check `isScrolledPastHalf()` was commented out, removing the requirement that the user must scroll past the halfway point before extra content loads. Now `isLoadingExtra` is set to `true` immediately when the scroll handler fires, allowing infinite scroll to trigger on any scroll event rather than only after passing the 50% threshold.
The `isScrolledPastHalf` function previously always returned true, causing continuous loading. Now it returns true only when scroll position is within the top quarter of the container, and the `loadExtra` guard condition is negated to trigger loading only when scrolled near the top.
The `shouldAutoScroll` function previously returned false when the user scrolled past the top 20% of the scrollable area, preventing automatic scroll-to-bottom behavior. By commenting out this conditional block, the function now always returns true, allowing the chat view to continuously auto-scroll to new messages regardless of the user's current scroll position. This enables an infinite-scroll-like behavior where the viewport follows incoming content without interruption.
The auto-scroll behavior in the chat messages container now triggers at a much lower scroll position (20% from bottom instead of 80%), allowing users to stay near the bottom of the conversation without being forcibly scrolled down. This enables a more natural infinite scroll experience where new messages only auto-scroll when the user is very close to the bottom of the chat history.
The condition for preventing auto-scroll on new messages was changed from
checking if the user is within the top fifth of the scrollable area to
checking if they are beyond the bottom 80% (i.e., scrolled past 83.3%).
This effectively inverts the logic so that auto-scroll is suppressed only
when the user has scrolled significantly upward, rather than when they are
near the top. The change ensures the chat view remains locked to the latest
message unless the user deliberately scrolls far up.
The scroll detection logic in the chat messages container now triggers auto-scrolling
when the user is within the top 20% (1/5) of the remaining scrollable area, instead of
the previous 25% (1/4) threshold. This reduces premature scroll jumps during message
streaming, allowing users to read older content with less interruption.
The change modifies the condition in `isNearBottom()` within `web.html` to trigger auto-scroll when the user is within the top quarter of the remaining scrollable area instead of the top half, making the scroll behavior more responsive to new messages appearing at the bottom.
The scrollbar is now hidden using `scrollbar-width: none` on the `.chat-messages` class, enabling a seamless infinite scroll experience without visible scrollbar interference.
- Added `overflow: hidden` to `.chat-container` to prevent body scroll interference
- Added `user-select: none` to `.chat-header` and `.avatar` to improve UX during scroll
- Removed scrollbar visibility by setting `display: none` on `::-webkit-scrollbar` while preserving scroll functionality
The scrollbar is visually removed across all major browsers (Firefox, IE/Edge, and WebKit) by setting `scrollbar-width: none`, `-ms-overflow-style: none`, and hiding the `::-webkit-scrollbar` pseudo-element. This enables a seamless infinite-scroll appearance while preserving actual scroll functionality.
The diff changes the pagination parameter from `offsetMessage.dataset.created_at` to `firstMessage.dataset.created_at` in the `getMessages` RPC call, ensuring the scroll-up infinite loading uses the correct timestamp anchor for fetching older messages.
Add scroll event listener to messages container that triggers loading of older messages when user scrolls to top. Fix indentation of isLoadingExtra flag to execute after all messages are inserted, ensuring correct state management during pagination.
Replace the previous approach that relied on detecting visibility of the 10th message element with a new mechanism that loads older messages when the user scrolls past the halfway point of the scrollable content. Introduce `isScrolledPastHalf()` helper to compute scroll position relative to total scrollable height, and add `isLoadingExtra` guard to prevent concurrent requests. Remove the now-unused `offsetMessage` element selection and `dataset.seen` tracking logic.
The offset element for loading additional chat messages was changed from the first child to the tenth child of the messages container, altering the scroll trigger point for infinite loading behavior.
The offset selector in loadExtra() was updated from nth-child(15) to nth-child(1), causing the infinite scroll mechanism to trigger loading of additional messages when the first message becomes visible rather than waiting for the 15th. This effectively makes the scroll loading activate much earlier in the chat history.
The RPC method call in the infinite scroll logic was using the snake_case name `get_messages` instead of the camelCase `getMessages` expected by the backend, causing the scroll-up pagination to silently fail when loading older messages.
Change the scroll-triggered message loading logic from checking the 4th message to the 15th message as the offset anchor, and remove console.info debug statements for channelUid and loaded messages.
Add optional `timestamp` parameter to `ChannelMessageService.offset()` method enabling cursor-based pagination alongside existing offset-based pagination. When timestamp is provided and page >= 0, query filters for messages created after the given timestamp; when page < 0, filters for messages created before the timestamp. Update `RPCView.get_messages()` to pass the new timestamp parameter through to the service. Also add `textBox.focus()` call in web template to automatically focus the message input field on page load.
Add `isElementVisible` helper to check if an element is within the viewport using `getBoundingClientRect`. Modify `updateLayout` to accept a `doScrollDown` parameter that controls whether the last message is scrolled into view. In the message receive handler, compute `doScrollDownBecauseLastMessageIsVisible` by checking if the last message exists and is visible, then pass it to `updateLayout` to prevent automatic scrolling when the user has scrolled up.
- Add `div` to the `.message-content` max-width rule to prevent overflow of block-level elements
- Expand `.message.switch-user .text` selector to include `iframe` and `video` alongside `img` for consistent border-radius and max-width styling across embedded media types
- Change route pattern from `/drive.bin/{uid}` to `/drive.bin/{uid}.{ext}` in app.py
- Modify UploadView to append file extension to the generated response link
- Remove conditional image markdown rendering in favor of generic url link with extension
The YouTube embed transformation code that replaced video links with iframe embeds has been commented out, effectively disabling the feature while preserving the original implementation for potential future re-enablement.
Add three new functions to src/snek/system/template.py that parse HTML anchor elements and replace YouTube links with iframe embeds, image links with img tags, and media file links with video/audio controls, enhancing the video player with automatic content embedding.
Detect YouTube video URLs by checking for "https://www.you" prefix and "?v=" parameter, extract the video ID, and replace the anchor element with a responsive embedded iframe player including standard YouTube embed attributes and security parameters.
The channel retrieval in the subscription loop was using the wrong key ('uid' instead of 'channel_uid'), causing lookups to fail for users subscribed to channels where their subscription uid differs from the channel uid. This fix ensures the correct field is used when fetching channel details for each subscription.
Include the channel's tag field when building the subscription list response, fetching the full channel object to populate the new "tag" key alongside existing name, uid, and permission fields.
- Change `self.sockets` from list to set to prevent duplicate socket entries
- Introduce `self.users` dict mapping user_uid to a set of their active sockets
- Add `send_to_user` method enabling direct message delivery to all sockets of a specific user
- Refactor `broadcast` to iterate over channel members from DB and use `send_to_user` instead of maintaining a separate subscription set
- Temporarily short-circuit `subscribe` method (returns immediately) as subscriptions are now derived from channel membership
- Update cache log to include LRU item count for better debugging visibility
- Rename `room` parameter to `channel_uid` in RPC `send_message` for consistency with service layer
- Add get_other_dm_user method to ChannelMemberService to retrieve the other participant in a DM channel
- Update WebView to display DM partner's nickname instead of channel label in sidebar
- Change sidebar links to use channel uid and name from the new channel list structure
- Fix message offset query to silently handle errors instead of crashing
- Replace scroll-to-bottom with scrollIntoView on last message for smoother UX
- Increase initial layout update delay from 200ms to 1000ms for better timing
Implement `ChannelService.get_for_user` to fetch non-banned, non-deleted channels for a given user by iterating through channel memberships. Replace hardcoded sidebar links in `app.html` with Jinja2 template loop rendering channels from the context, enabling dynamic display of user-accessible chat rooms.
The echo method sends the received object directly via websocket and returns the string "noresponse", which prevents the RPC handler from sending an additional JSON response. This allows clients to receive their echoed data without the standard callId/success wrapper.
Add extension type mapping and conditional response generation in UploadView to return iframe embeds for video, audio, and document files instead of markdown image syntax. Introduce base_url property on BaseView to construct absolute embed URLs.
When a user attempts to create a private chat with another user, the system now checks for an existing direct message channel and redirects to it instead of proceeding to create a duplicate. This prevents duplicate DM channels and provides a seamless user experience by reusing the existing conversation.
When a user navigates to a private chat page with another user, the handler now checks if a direct message channel already exists between them. If found, it immediately redirects to that channel's page instead of proceeding to create a new one, preventing duplicate DM channel creation and improving navigation flow.
The tag parameter for direct message channels was changed from uppercase "DM" to lowercase "dm" in the ChannelService.create call, ensuring consistent tag formatting across the codebase for private chat channels.
The create_dm method in ChannelMemberService previously passed a "dm" tag to the create method for both the from_user and to_user memberships. This change removes the tag parameter from both create calls, simplifying the direct message channel member creation logic.
Implement get_dm method in ChannelService that finds existing DM channels via channel_member join query or creates new ones with 'DM' tag. Add get method with fallback lookup by uid, name, and #name. Fix channel_member is_banned check to use dict access. Update web view to resolve channels by uid or user uid, and fix template references from channel_uid to uid.
Previously the channel service yielded users whose last ping was 20 or more seconds ago, effectively showing offline users. The condition is now flipped to `< 20` so only users with a recent ping (within the last 20 seconds) are returned as online.
The change modifies the time delta comparison in the `_iter_online_users` generator within `ChannelService`, flipping the condition from `< 30` to `>= 20` seconds since last ping. This effectively lowers the threshold for considering a user online, making the presence detection more responsive and reducing the window for stale status display.
The `get_users` generator in `ChannelService` previously yielded the result of `self.services.user.get()` unconditionally, which could return `None` for deleted or missing users. This change adds a guard to only yield when a valid user object is returned, preventing downstream consumers from receiving `None` entries. This is a prerequisite for the `get_online_users` method to correctly filter online status without encountering null references.
Introduce a `last_ping` string field on the UserModel to store the timestamp of the user's last activity. Implement `get_online_users` in ChannelService that filters channel members whose `last_ping` is within the last 30 seconds. Add a `ping` RPC endpoint that updates the user's `last_ping` to the current server time. On the client side, emit a "connected" event on WebSocket open, trigger an immediate "online" ping, and schedule a recurring "active" ping every 15 seconds to maintain the online status.
Add the `Access-Control-Allow-Credentials: true` header to both the preflight
OPTIONS handler and the main request handler in `cors_allow_middleware` and
`cors_middleware` functions, enabling credentialed cross-origin requests.
The previous rule `.message:has(+ .message.switch-user), .message:last-child .time` incorrectly applied `display: block` to `.time` only when it was a direct child of `.message:last-child`, but failed to scope it properly for the `:has()` selector. The fix wraps both selectors in a parent rule `.message:has(+ .message.switch-user), .message:last-child` and nests `.time { display: block; }` inside, ensuring consistent behavior across both cases.
The change modifies the chat message insertion logic in web.html to append
`message.firstChild` rather than the entire `message` element, ensuring only
the inner content is added to the chat-messages container. This prevents
wrapping the HTML fragment in an extra div layer, preserving the expected
DOM structure for subsequent layout updates.
The change replaces `message.firstChild` with `message` when appending to `.chat-messages`, ensuring the complete message wrapper (including outer div with data attributes) is added to the DOM rather than just its inner content. This preserves the element's dataset properties for subsequent interactions.
The commit adds a 200ms setTimeout call to trigger updateLayout() a second time after a new chat message is appended to the DOM. This addresses a layout timing issue where the initial synchronous updateLayout() call may execute before the browser has fully rendered the newly inserted message element, causing incorrect scroll or sizing calculations. The delayed call ensures layout metrics are recalculated after the browser's rendering pipeline has completed for the new content.
Add WebKit scrollbar customization rules (::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb) and Firefox scrollbar properties (scrollbar-width: thin, scrollbar-color) to the .chat-messages element, using a 6px width, #888 thumb with 3px border-radius, and #f1f1f1 track background for a minimal aesthetic.
Add `color` field to message template context in ChannelMessageService. Implement `to_extended_dict` method to return full message data including user color and username. Add `offset` method for paginated message queries with descending order by creation time. Fix WebSocket event emission in app.js to use correct event name. Add `timeDescription` and `timeAgo` utility functions for client-side timestamp formatting. Enable scrollable chat messages container with overflow-y auto. Add CSS rules for message content block display and responsive image sizing. Fix scrollIntoView behavior to align to block end. Correct service worker registration path. Fix upload button to use local channelUid attribute. Strip trailing dots from link hrefs and exclude trailing dots from URL regex. Remove redundant script imports from app.html template. Uncomment message HTML template structure with proper time element. Replace chat-window element with server-rendered message list in web.html template. Refactor RPC get_messages to use new offset-based query with extended message dict.
- Add `/channel/{channel}.html` route to router in `app.py`
- Fix `LoginView.submit` to call `form.is_valid()` as a method instead of property
- Implement `WebView.get` with channel lookup, member check, and redirect logic
Add full MIT license text and author credit (`retoor@molodetz.nl`) as
file-level comments across all view files (`about.py`, `docs.py`,
`index.py`, `login.py`, `login_form.py`, `logout.py`, `register.py`,
`register_form.py`). Also fix `form.is_valid` to `form.is_valid()` in
login views and consolidate session assignment into a single `update()`
call in `login.py`.
Implement a new search_user.html template extending the app layout, featuring a GET form with a required username input and a submit button, plus an unordered list displaying matched users as clickable links to their profile pages.
The old `search-user.html` template is deleted entirely, and the `SearchUserView` now renders `search_user.html` instead, aligning the template reference with the new naming convention.
Add a reusable `.no-select` CSS class with cross-browser user-select: none properties
and apply it to navigation links, sidebar headings, chat room links, and avatar
elements in the message list to improve UI consistency and prevent accidental
text selection during interaction.
- Add PRAGMA journal_mode=WAL query to improve concurrent read performance
- Add PRAGMA syncnorm=off query to reduce disk synchronization overhead
- Both pragmas executed immediately after router setup in Application.__init__
- Replace generic-form with custom form containing text input and fancy-button
- Add CSS for anchor links, chat-area list items, and text input styling
- Update search_user view to handle query parameter and render user results
- Register fancy-button and generic-form scripts in app.html template
- Modify fancy-button to support form submission via 'submit' url attribute
Implement a new form component for searching users, including a required username input field with length constraints (1-128 characters) and a submit button labeled "Search". The form is structured under the `snek.form.search_user` module and extends the base `Form` class with a title element.
Add SearchUserView with HTML and JSON endpoints, create search-user.html template extending app.html with navigation link, implement push.js for service worker registration and push subscription handling, add service-worker.js for push event display and notification click handling, and fix install prompt event listener registration in app.html
- Allow hyphens in username regex in LoginForm and UserModel
- Add plus and forward slash to allowed username characters in UserModel
- Replace password minimum length regex with min_length=1 constraint in all three files
Create the main application template for the Snek chat interface, including header navigation with home, install, users, settings, and logout links, a sidebar listing chat rooms (General, Development, Support, Random), and a chat-window component in the main content area. Include script references for push, upload-button, html-frame, schedule, app, models, message-list, message-list-manager, chat-input, and chat-window modules, along with base.css stylesheet and manifest.json for PWA support. Add a beforeinstallprompt event listener that stores the install prompt event and displays an alert for triggering the PWA installation flow.
Updated the nav links in src/snek/templates/web.html to use emoji characters
instead of plain text labels: Home→🏠, Install→📥, added 👥 link, Settings→⚙️,
Logout→🔒.
Introduce a new `get_lexer` method that attempts to retrieve a Pygments lexer by the given language name, falling back to 'bash' if the language is unknown. Refactor `block_code` to use this method, removing the direct `get_lexer_by_name` call and the fallback HTML escape block for missing lexer results.
- Add binary gif file src/snek/static/emoji/snek1.gif
- Register :snek1: emoji mapping in template.py with alias and status
- Swap pywebpush dependency for PyJWT in pyproject.toml
- Reorder emoji, linkify, markdown processing order in message.html template
Fix the non-working upload button by updating the CSS selector from `.chat-input button` to `.chat-input upload-button` in base.css, and restore the button disabled state logic that was lost during the refactor in chat-input.js. Also add missing semicolons and fix the debug log reference to undefined `params` variable in RESTClient.post.
Remove the zero-height, zero-padding, and zero-margin rules from the .avatar class within the narrow viewport media query, reverting the avatar to its default visible sizing.
The change modifies the `.avatar` class inside a narrow `message-list` media query, swapping `display: none` for explicit zero `height`, `padding`, and `margin` to preserve layout flow while visually hiding the element.
The commit removes the `rooms` array and its initialization from the `App` constructor, and changes the `user` property assignment from an `await` call to a promise-based `.then()` callback. This ensures that `me.user` is set asynchronously after the constructor completes, preventing the notification system from triggering alerts for messages sent by the current user before their identity is fully loaded.
Add pywebpush dependency to pyproject.toml for future push notification support.
Remove unused Message, Messages, Room, InlineAppElement, and Page classes from app.js.
Add user property to ChatWindowElement and fetch current user in App constructor.
Modify message event handler to only play notification sound when message user_uid differs from current user.
Add a new highlight.css file under src/snek/static/ providing Pygments-compatible CSS classes for syntax highlighting. The stylesheet defines color and font-style rules for over 40 token types including comments, keywords, operators, strings, numbers, and generic markup elements, with a default monospace pre block and line-number styling for linenos spans.
Insert a <link> tag referencing /highlight.css before the message rendering block in message.html to enable syntax highlighting for code blocks within chat messages.
- Add 'emoji' to project dependencies in pyproject.toml
- Create EmojiExtension class in template.py that parses {% emoji %}...{% endemoji %} blocks and converts emoji aliases (e.g., 😄) to Unicode emoji using emoji.emojize with language='alias'
- Register EmojiExtension in Application's Jinja2 environment
- Wrap message content with {% emoji %} tags in message.html template to enable emoji rendering in chat messages
The regex pattern for the username field in UserModel was updated from
`^[a-zA-Z0-9_]+$` to `^[a-zA-Z0-9_-+/]+$`, adding support for hyphens,
plus signs, and forward slashes in usernames.
Added a new `query` method to `RPCView` that requires login and validates
incoming queries by rejecting any containing dangerous SQL keywords
(drop, alter, update, delete) before executing the query against the
channel service and returning results as a list of dictionaries.
Changed keyup listener to keydown for the textBox in ChatInputElement. Now checks for shiftKey modifier: if shift is held, Enter inserts a line break without submitting; otherwise, it prevents default, dispatches submit event, and clears the input value. This fixes accidental submission when composing multi-line messages.
Uncomment and activate the previously disabled WebSocket subscription flow in the `RPCView` handler. When a user's session indicates they are logged in, the code now adds the WebSocket connection to the socket service, queries the `channel_member` service for non-deleted, non-banned memberships, and subscribes the socket to each associated channel UID. This restores real-time channel subscription behavior for authenticated sessions.
Temporarily comment out calls to `self.services.socket.delete(ws)` in both the
`WSMsgType.ERROR` and `WSMsgType.CLOSE` branches of the WebSocket handler in
`src/snek/view/rpc.py`, replacing them with `pass` to prevent premature
connection cleanup during unbuffered message processing.
The print statement in the websocket message loop within RPCView's
subscription handler was missing flush=True, causing output buffering
and delayed log visibility during long-running websocket connections.
Adding flush=True ensures immediate stdout output for real-time
debugging and monitoring of incoming websocket messages.
The `send_text` method on the websocket connection was deprecated and has been replaced with `send_str` to ensure compatibility with the current websocket library API. This change affects the JSON serialization and transmission path for all RPC responses sent through the `_send_json` helper method in the `RPCView` class.
The _send_json and call_ping methods were incorrectly indented at the class level instead of being nested inside the try-except block of the parent method. This fix moves them to the proper indentation level within the class body, restoring correct method scope and preventing potential AttributeError or unexpected behavior when these methods are called.
The diff replaces all direct `self.ws.send_json()` calls with `self._send_json()` which serializes JSON using `json.dumps` with `default=str` for non-serializable objects. Additionally, the `rpc()` call in the message handler is wrapped in a try-except block that logs exceptions and deletes the websocket connection on failure. A new `_send_json` async method is introduced to centralize JSON sending logic.
Add `print(result, flush=True)` after constructing the exception result dictionary in the RPCView method execution error handler, ensuring the error details are immediately written to stdout without buffering.
- Replace bare `except:` with `except IndexError:` in SocketService.delete
- Move socket.delete call inside ERROR handler to avoid premature cleanup
- Add explicit CLOSE handler to delete socket on clean WebSocket close
The debug print statement that logged the subscription label during WebSocket
connection setup has been commented out to reduce noise in server logs during
normal operation.
The diff shows two changes in `src/snek/view/rpc.py`:
1. In the error handling block, the `callId` and `success` fields are removed from the exception result dict, changing from `{"callId":call_id,"success": False, "data":{"exception":str(ex),"traceback":traceback.format_exc()}}` to `{"exception":str(ex),"traceback":traceback.format_exc()}`.
2. The entire session-based subscription logic (checking `logged_in`, adding websocket, subscribing to channels) is commented out, leaving only the `print` statement active.
Ensure debug output for WebSocket exceptions and socket deletion is immediately visible by enabling unbuffered stdout. Also fix missing trailing newline at end of file in the pass statement block.
Add a flush-enabled print statement to output the messages list after
appending the formatted message in the send_message method of RPCView.
This aids in debugging by exposing the accumulated response messages
directly to stdout during execution.
Set PYTHONUNBUFFERED="1" in environment for both snek and snecssh services in compose.yml to disable Python output buffering, ensuring real-time log visibility from gunicorn and snekssh processes.
Add DockerfileDrive for containerized deployment with Python 3.12.8 Alpine base, including SSH host key and pip setup. Implement multiple SSH server variants: app.py with SFTP file transfer support on port 2225, app2.py with custom bash shell spawning, app3.py with terminal resize handling, app4.py with bcrypt password authentication, and app5.py with chat client infrastructure. Include TileGridElement and UploadButton custom web components in media-upload.js for gallery-style image display with hover effects.
Add snecssh service to compose.yml with asyncssh dependency in pyproject.toml. Introduce gallery and tile CSS classes in base.css for image grid layout. Include commented-out tile-grid and upload-button elements in chat-window.js. Load media-upload.js script in web.html template. Wrap RPC method execution in try-except to return structured error response with exception message and formatted traceback.
- Include `username` from user data when constructing `ChannelMessage` in `ChatService`
- Append `username` to message dict in `RPCView` response alongside existing `user_nick` field
Add early return in linkify_https when the input text does not contain
"https://", preventing unnecessary BeautifulSoup parsing and regex
matching for non-link content.
Add timeAgo() and timeDescription() methods to MessageListElement for displaying relative timestamps (e.g. "2 hours ago") alongside formatted time. Store raw ISO date in dataset for future use. Update base.css to constrain inline images within .message .text to 90% width with rounded corners, and apply same styling to .message.switch-user .text images.
Add `requests` to project dependencies in pyproject.toml. Register `LinkifyExtension` and `PythonExtension` from new `snek/system/template.py` module in the application's jinja2 environment. Apply `max-width: 100%`, `word-wrap`, `overflow-wrap`, and `hyphens` to `.message` class in base.css. Wrap message template content in `{% linkify %}...{% endlinkify %}` tags to auto-convert bare URLs to clickable links with `target="_blank"`, `rel="noopener noreferrer"`, and `referrerpolicy="no-referrer"`.
Extract the `playSound` method and related sound assets from the `App` class into a new `NotificationAudio` class with configurable timeout scheduling. Fix the `Schedule.cancelDelay` method to correctly reference `this.timeOut` instead of the incorrectly named `this.interval` property. Remove the `linkifyText` call from `MessageListElement` to render raw HTML content directly.
The diff shows a single-line change in `src/snek/templates/message.html` where a closing comment marker `{#}` was moved from the line after the `{% endmarkdown %}` block to the line before the `</div><div class="time">` element. This fixes a template rendering issue where the comment was incorrectly positioned, likely causing the time div to be commented out or the markdown block to be improperly closed. The change ensures the comment wraps only the intended content and the template structure remains valid.
Add a new `snek.docs` application module that serves documentation from a configurable path. The application extends the base `Application` class, integrates a Markdown extension for Jinja2 templates, and routes all requests under `/{tail:.*}` to a document handler that resolves file paths, returns 404 for missing resources, and renders templates. The commit also includes initial documentation pages: an API reference, a base layout with dark theme styling, a form API JavaScript guide, a form API Python example with custom validation, and an index page introducing the Snek chat application.
This commit deletes the documentation subsystem including the aiohttp-based Application class, all Jinja2/Markdown templates (base.html, index.html, api.html, form_api_python.html, form_api_javascript.html), and associated CSS styling. The removal eliminates the MarkdownExtension integration, the catch-all route handler for document serving, and all rendered documentation pages from the repository.
Add optional `html` field to ChannelMessageModel for storing rendered markdown. Extend ChannelMessageService.create() to render message content using Jinja2 template with user context, broadcasting the html alongside plain text. Update frontend MessageModel and MessageListElement to accept and display the html field when present, falling back to linkified plain text.
The install button anchor element now has a style="display:none" attribute
applied directly in the HTML template, preventing it from being visible
on initial page load until explicitly shown by JavaScript logic.
- Changed the navigation anchor from "Rooms" to "Install" with id="install-button"
- Removed the hidden `<fancy-button>` element from the sidebar, consolidating the install UI into the nav bar
The script tag for html-frame.js had a typo where `sr=` was used instead of `src=`, causing the browser to fail loading the resource. This fix restores the correct attribute name so the script loads properly.
Add conditional logic to the MessageListElement class that applies a
"switch-user" CSS class to message elements when the current message's
user differs from the previous one, or when it is the first message in
the list. This enables visual separation between messages from different
users in the chat interface.
Implement conditional visibility for message metadata in chat UI: avatar and author are hidden by default on `.message` and shown only when `.switch-user` class is present; time is hidden unless the next sibling has `.switch-user`, reducing visual clutter for back-to-back messages from the same sender.
Introduce a new utility service class that extends BaseService, providing an async method to generate random hex color codes with RGB values constrained between 128 and 255 for consistently light/pastel shades.
Add uid, channel_uid, user_nick, created_at, user_uid, and message as data attributes on each message div element in MessageListElement.createElement, enabling direct access to message properties from the DOM without requiring a separate data lookup.
- Wrap WebSocket creation in try-catch with 1s retry in Chat class
- Add ensureTimer to Socket class for periodic 5s reconnection attempts
- Comment out background and box-shadow in chat message CSS
- Add beforeinstallprompt listener in ChatWindowElement for PWA installation
- Link manifest.json in web.html template for progressive web app support
- Convert subscription list to set in broadcast to prevent duplicate sends to same client
- Add notification sound playback when a new message is received in chat window
- Refactor chat-input element to cache textarea reference and fix event listener binding
- Add binary MP3 sound effect files (alarm1, alarm2, beep1, beep2, beep3) to audio directory
- Integrate app.playSound(0) call in ChatWindowElement message event handler to trigger notification sound when new messages arrive
- Apply minor whitespace and formatting cleanup across app.js, message-list.js, and related files
The benchMark method now accepts an optional `message` parameter that defaults to "Benchmark Message" when not provided, replacing the hardcoded "Haha" prefix in sent messages. The console.info call for sendMessage response data has also been removed.
- Remove console.info("ROCKSTARTSS") and setTimeout wrapper from chat-window.js scrollIntoView call
- Add messageEventSchedule property to MessageListElement for debouncing message events
- Create new Schedule class in schedule.js with delay, repeat, and cancel methods
- Replace direct dispatchEvent with debounced call via messageEventSchedule.delay()
- Initialize messageEventSchedule with 500ms delay in connectedCallback
- Add schedule.js script tag to web.html template
The query in `get_messages` method of `RPCView` class was changed from an unbounded `SELECT * FROM channel_message` to `SELECT * FROM channel_message ORDER BY created_at DESC LIMIT 30`, enforcing a 30-row cap on all table fetches to prevent unbounded result sets.
Remove the DOMContentLoaded event listener that called app.benchMark(3) after a 1-second delay, eliminating the automatic benchmarking on page load from the chat interface template.
- Stripped two `console.info` calls in `chat-window.js` that logged channel and messages objects
- Removed the hardcoded test `sendMessage("Grrrrr")` call in the same file
- Deleted a stray `console.info("WIIIIIIIIIIIIIIIIIIIIIIII")` from `message-list.js` event listener
Implement a custom HTML element 'chat-input' that provides a styled textarea and send button. The component dispatches custom events for input, change, and submit actions, with Enter key support for submission and automatic button disabling when the textarea is empty.
Extract inline notification logic from ChatService.send into a new NotificationService.create_channel_message method, register NotificationMapper and NotificationService in their respective factories, and refactor the chat frontend to use custom elements (message-list, chat-input) with proper CSS layout and event-driven message rendering.
Implement a new custom HTML element 'chat-window' that initializes a shadow DOM, fetches the first available channel via RPC, displays its name in a header, and renders a message-list child element bound to that channel. Also includes initial debug logging of channel data and a test message send on connection.
- Add async query method to BaseMapper yielding dict records from raw SQL
- Add async query method to BaseService delegating to mapper.query
- Wrap ws.send_json in try/except to prevent broadcast failure on disconnected clients
- Fix localhost WebSocket URL to include port 8081
- Include chat-window.js script in web.html template
- Replace static chat-area markup with custom <chat-window> element
- Implement get_messages RPC method fetching last 30 channel messages with user nicknames
- Added `url` property to Socket class to store the computed WebSocket endpoint
- Set URL to `ws://localhost/rpc.ws` when hostname is localhost, otherwise use `wss://` with the current hostname
- Updated WebSocket constructor in connection logic to use `this.url` instead of hardcoded `ws://localhost:8081/rpc.ws`
Implement core chat functionality including ChatService for message creation and notification dispatch, SocketService for WebSocket subscription management and broadcasting, RPC endpoint for client-server communication, and frontend components (message-list, message-list-manager, MessageModel) for rendering messages in real-time across channels.
Introduce a new `/rpc.ws` WebSocket route with `RPCView` handler, along with `NotificationMapper` for database persistence. Register `ChannelMessageService`, `ChatService`, and `SocketService` in the service container. Refactor frontend by replacing static chat messages with a `message-list-manager` custom element, adding `Socket` class for WebSocket connection management, and including new JavaScript modules (`models.js`, `message-list.js`, `message-list-manager.js`). Also add a debug print in `ChannelMemberService.create` and expose `services` property on `BaseView`.
Remove unused `from types import SimpleNamespace` imports across app.py, mapper/__init__.py, and service/__init__.py. Reformat all model, service, and mapper files to use consistent keyword argument spacing, add missing trailing newlines at end of files, and align multiline dict/function call formatting with PEP 8 standards across channel, channel_member, channel_message, and notification modules.
Introduce ChannelMapper, ChannelMemberMapper, ChannelMessageMapper classes and corresponding ChannelModel, ChannelMemberModel, ChannelMessageModel, NotificationModel definitions. Register new mappers and models in get_mappers/get_models factories using Object container. Add ChannelService with create and ensure_public_channel methods, ChannelMemberService with create method handling banned user checks, and ChannelMessageService with create method. Refactor Application to instantiate Cache, flatten services/mappers assignment, and replace LoginFormView/RegisterFormView with LoginView/RegisterView for /login.json and /register.json routes. Extend UserModel with optional nick field.
- Add aiohttp-session and cryptography dependencies to pyproject.toml
- Configure EncryptedCookieStorage with static key in Application init
- Implement custom session middleware attaching session to request
- Add login_required flag and redirect logic to BaseView._iter
- Store uid, username, and logged_in flag in session on registration
- Return redirect_url from RegisterFormView.submit for client-side navigation
- Add RESTClient class in app.js with GET/POST methods and debug logging
- Wire GenericForm to redirect on successful submit via saveResult.redirect_url
- Add /status.json route with StatusView placeholder
- Fix UUIDField value property to always return string
- Remove debug console.log statements from generic-form.js
- Update web.html title and logo to "Snek" and include app.js script
- Mount DocsApplication at /docs path in main Application router
- Move MarkdownExtension from jinja_markdown2 to snek.system.markdown module
- Add _allow_harmful_protocols flag and SimpleNamespace import to markdown renderer
- Update index.html link from /docs.html to /docs/docs/
Implement async to_json and is_valid across form, model, and validator layers to support client-side form validation via fetch. Introduce UsernameField with async uniqueness check against user service count. Wire up services and mappers as SimpleNamespace on app startup. Refactor UserService to extend BaseService and rename create_user to register with email parameter. Fix UserMapper model_class attribute name.
Add three new files: docs.html template extending base layout with back button and markdown frame, docs.md containing API usage examples for user registration, string encryption, and view classes, and docs.py with DocsHTMLView and DocsMDView handlers rendering the respective templates.
- Add DocsHTMLView and DocsMDView routes for API documentation pages
- Disable time_cache_async decorator on render_template method
- Replace functools.cache with time_cache_async(120) on render_markdown
- Switch compose entrypoint from python -m snek.app to gunicorn with aiohttp worker
- Add API docs link to index.html navigation and update existing link text
Implement a new `src/snek/system/cache.py` module providing two caching mechanisms:
- `cache`: a direct alias for `functools.cache` for synchronous function memoization
- `async_cache`: a custom decorator that caches results of async functions based on positional arguments, using an internal dictionary to store and retrieve previously computed values
Add `time_cache_async` decorator to `render_template` in `Application` class for 60-second caching of rendered templates. Refactor `render_markdown` to use a synchronous `@functools.cache` wrapper `render_markdown_sync` to avoid redundant `MarkdownRenderer` instantiation. Remove debug `print` statements from `Validator` and `BaseModel` in `model.py`. Rename form endpoints from `/login-form.json` and `/register-form.json` to `/login.json` and `/register.json` respectively, updating corresponding template references. Inject inline `<style>{{ highlight_styles }}</style>` into `base.html` for syntax highlighting CSS. Append extensive internal documentation to `about.md` covering user registration, encryption, view creation, and form handling.
Handle the special "/back" and "/back/" URL values in FancyButton click handler to call window.history.back() instead of navigating to a fixed page. Update the about.html template to use "/back" for the Back button, enabling proper browser history traversal.
- Introduce UserService with create_user method and password hashing
- Add UserMapper and BaseMapper for database operations
- Create service, mapper, and model registry modules with cached factories
- Rename User model class to UserModel for consistency
- Make email field optional in both RegisterForm and UserModel
- Integrate MarkdownExtension into Jinja2 environment
- Add AboutHTMLView and AboutMDView routes with .html and .md endpoints
- Append jinja-markdown2 and mistune to project dependencies
- Implement markdown rendering in html-frame.js for .md file responses
- Create markdown-frame.js custom element for standalone markdown display
- Add style.css with dark theme and orange heading colors
- Update Dockerfile to Python 3.12.8-alpine3.21 and fix CMD syntax
- Extend .gitignore with .resources, .backup*, and docs directories
- Refactor form.py with MIT license header and code documentation
- Add license and documentation headers to system/http.py
- Fix mobile form layout with full-width and full-height constraints
Replace the Dockerfile CMD from gunicorn to `python -m snek.app`, add MIT LICENSE.txt, populate pyproject.toml with project metadata and dependencies, refactor forms into `snek.form.login` and `snek.form.register` with new HTMLElement system, delete old `snek/forms.py`, add `snek/model/user.py` with User model, restructure `snek/app.py` imports and router setup to use new view modules, rename `styles.css` to `base.css` and strip comments, add `fancy-button.js` custom element, and update `compose.yml` entrypoint accordingly.
Remove the develop watch configuration block that enabled live code sync
and add a restart: always policy to the snek service for production
deployment readiness.
Add Dockerfile with wkhtmltopdf multi-stage build and gunicorn entrypoint for production deployment. Introduce compose.yml with volume sync for development. Switch Makefile from snek.serve to gunicorn with aiohttp worker. Extend Application class with CORS middleware, static file serving, login/test/http-get/http-photo routes, and template rendering. Create gunicorn.py entrypoint, http.py module with async URL fetching, screenshot generation via imgkit, link repair, and CRC32-based caching. Add middleware.py with CORS handlers. Include static assets: app.js with Message/Room/App classes, html_frame.css/js custom element, and prachtig-gitter_like.html UI template. Update setup.cfg dependencies with beautifulsoup4, gunicorn, imgkit, wkhtmltopdf. Add *.png to .gitignore.
- Rewrite README to describe new project purpose: a performance-focused Slack-like chat app
- Remove old Matrix bot description and add installation and running instructions
- Add make install and python module run commands for snek.app and snek.models
Set up the Python project skeleton including pyproject.toml, setup.cfg, Makefile, .gitignore, and README. Implement the core snek application with aiohttp-based web server, model validation framework, and a registration form with username, email, and password fields.
2025-01-17 22:06:17 +00:00
315 changed files with 29199 additions and 3820 deletions
Increases the websocket heartbeat interval to reduce network overhead and improve connection efficiency. Loads subscriptions asynchronously to enhance responsiveness during data retrieval.
**Changes:** 1 files, 29 lines
**Languages:** Python (29 lines)
## Version 1.35.0 - 2026-01-31
update py files
**Changes:** 1 files, 2 lines
**Languages:** Python (2 lines)
## Version 1.34.0 - 2026-01-31
update css, html, py files
**Changes:** 44 files, 3802 lines
**Languages:** CSS (712 lines), HTML (1300 lines), Python (1790 lines)
## Version 1.33.0 - 2026-01-24
Modularize RPC action handlers by splitting the monolithic rpc.py file into separate components for authentication, channels, containers, databases, mentions, messages, and miscellaneous actions. This improves code organization and facilitates easier maintenance and extension of RPC functionalities.
**Changes:** 13 files, 2753 lines
**Languages:** Python (2753 lines)
## Version 1.32.0 - 2026-01-24
Adds mention functionality to the chat system, enabling users to mention other users with backend models and services handling the logic. Updates the frontend with navigation for mentions, including modified chat input and new mention navigation components.
Reduces the maximum number of workers for channel message processing to 1, limiting concurrency to improve performance in resource-constrained environments.
**Changes:** 1 files, 2 lines
**Languages:** Python (2 lines)
## Version 1.30.0 - 2026-01-17
Increases the maximum number of workers for processing channel messages to 30, enabling better handling of concurrent message loads.
**Changes:** 1 files, 2 lines
**Languages:** Python (2 lines)
## Version 1.29.0 - 2026-01-17
Updates the site's CSS and HTML to refine the visual layout and styling of the index page. These changes enhance readability and user interface consistency without altering core functionality.
**Changes:** 2 files, 297 lines
**Languages:** CSS (293 lines), HTML (4 lines)
## Version 1.28.0 - 2026-01-17
Adds a documentation hub with pages on architecture, design, API, bots, and contributions. Increases the maximum workers in the channel message service to 10 for improved performance and updates navigation links in the about and index pages.
**Changes:** 11 files, 2755 lines
**Languages:** HTML (2719 lines), Python (36 lines)
Users can now create, configure settings for, and delete channels through dedicated dialog interfaces. Developers access new RPC methods to support these channel management operations.
**Changes:** 9 files, 801 lines
**Languages:** CSS (127 lines), HTML (517 lines), Python (157 lines)
## Version 1.13.0 - 2025-12-24
Improves performance in the balancer, socket service, and cache by removing async locks. Adds database connection injection for testing in the app and updates pytest configuration.
**Changes:** 5 files, 291 lines
**Languages:** Other (6 lines), Python (285 lines)
## Version 1.12.0 - 2025-12-21
The socket service enhances concurrency and performance through improved locking and asynchronous handling. The RPC view renames the scheduled list to tasks for clearer API terminology.
**Changes:** 2 files, 243 lines
**Languages:** Python (243 lines)
## Version 1.11.0 - 2025-12-19
Adds the ability to configure whether user registration is open or closed via system settings. Administrators can now toggle registration openness to control access to the registration form.
**Changes:** 5 files, 262 lines
**Languages:** HTML (185 lines), Python (77 lines)
## Version 1.10.0 - 2025-12-19
Users must now receive an invitation to register for an account, as open registration is disabled.
**Changes:** 1 files, 6 lines
**Languages:** HTML (6 lines)
## Version 1.9.0 - 2025-12-18
Adds a debug logging option to the serve command for enhanced troubleshooting. Improves error handling across the application and corrects a typo in the ip2location middleware.
The socket service now handles errors more robustly and prevents crashes through improved safety checks. Socket methods support better concurrency and provide enhanced logging for developers.
Fixes socket cleanup in the websocket handler to prevent resource leaks and improve connection stability.
**Changes:** 1 files, 29 lines
**Languages:** Python (29 lines)
## Version 1.6.0 - 2025-12-17
Removes presence debounce to make user departures instant. Simplifies websocket connection and error handling in RPC views for improved reliability.
**Changes:** 2 files, 98 lines
**Languages:** Python (98 lines)
## Version 1.5.0 - 2025-12-17
remove umami analytics script
**Changes:** 1 files, 1 lines
**Languages:** HTML (1 lines)
## Version 1.4.0 - 2025-12-17
Updates the socket service to improve connection stability and error handling.
**Changes:** 1 files, 4 lines
**Languages:** Python (4 lines)
## Version 1.3.0 - 2025-12-17
Users now receive notifications when other users join or depart the application. Departure notifications are debounced to reduce the frequency of rapid successive alerts.
**Changes:** 5 files, 418 lines
**Languages:** HTML (1 lines), JavaScript (259 lines), Python (158 lines)
## Version 1.2.0 - 2025-12-17
Removes Umami analytics integration, eliminating user tracking functionality. Developers must handle analytics separately if needed.
**Changes:** 2 files, 2 lines
**Languages:** HTML (1 lines), Python (1 lines)
## Version 1.1.0 - 2025-12-17
Fixes potential errors in forum message handling by adding a null check for the star field, preventing crashes when the field is missing. Updates the message list display to handle starred messages more reliably.
The Snek project is a comprehensive web-based application, functioning as a collaborative platform or chat system with extensive features including user management, channels, repositories, containers, real-time communication via WebSockets, Docker integration, Git support, and more. It leverages Python (primarily with aiohttp for the backend) and JavaScript for the frontend. The codebase is organized into modules such as app, mapper, model, service, view, system, and static assets. This review is based on a literal examination of every Python file in `src/snek/*` recursively, as their full contents have been provided.
## Strengths
- **Modular Architecture**: The code exhibits strong separation of concerns with dedicated modules for mappers (data access), models (data structures), services (business logic), views (HTTP/WebSocket handlers), and system utilities. This promotes maintainability and scalability.
- **Asynchronous Support**: Extensive use of async/await throughout (e.g., in `src/snek/app.py`, `src/snek/service/socket.py`, `src/snek/view/rpc.py`), effectively handling I/O-bound operations like WebSockets, database queries, and external API calls.
- **Feature-Rich**: Supports a wide array of features, including user authentication, file uploads, terminal emulation, Docker container management, Git repositories, WebDAV, SSH, forums, push notifications, and avatar generation. Integration with external tools like Docker and Git is notable.
- **Frontend Components**: Custom JavaScript components (e.g., in `src/snek/static/njet.js`) provide a modern, component-based UI with event handling and REST clients.
- **Caching and Utilities**: Robust caching implementation (e.g., in `src/snek/system/cache.py`) with LRU eviction, and utility services (e.g., `src/snek/service/util.py`) enhance performance and reusability.
- **Model and Field System**: A sophisticated model system (e.g., in `src/snek/system/model.py`) with typed fields (e.g., UUIDField, CreatedField) ensures data integrity and validation.
- **Extensibility**: The codebase includes hooks for extensions (e.g., Jinja2 extensions in `src/snek/system/markdown.py`, `src/snek/system/template.py`) and sub-applications (e.g., forum in `src/snek/forum.py`).
- **Error Handling in Places**: Some areas show good error handling (e.g., try-except in `src/snek/service/socket.py`, `src/snek/view/rpc.py`), though not universal.
## Weaknesses
- **Syntax Errors**:
- In `src/snek/research/serpentarium.py`, line 25: `self.setattr(self, "db", self.get)`–`setattr` is misspelled; it should be `setattr`. Additionally, the line ends with `)`, which is misplaced and causes a syntax error. Line 26: `self.setattr(self, "db", self.set)`– similar misspelling and incorrect assignment (self.set is not defined).
- In `src/snek/sync.py`, line 25: `self.setattr(self, "db", self.get)`–`setattr` misspelled and misplaced `)`. Line 26: `self.setattr(self, "db", self.set)`– same issues. Line 27: `super()`– called without arguments, which may fail in classes with multiple inheritance or if arguments are expected.
- **Security Concerns**:
- **Injection Vulnerabilities**: Raw SQL queries without parameterization (e.g., in `src/snek/service/channel.py`, `src/snek/service/user.py`, `src/snek/view/rpc.py`) risk SQL injection. For example, in `src/snek/service/channel.py`, queries like `f"SELECT ... WHERE channel_uid=:channel_uid {history_start_filter}"` use string formatting, but not all are parameterized.
- **Authentication Gaps**: Some WebSocket and RPC endpoints lack visible authentication checks (e.g., in `src/snek/view/rpc.py`, methods like `echo` and `query` don't enforce login). Basic auth in `src/snek/webdav.py` and `src/snek/sssh.py` is present but hardcoded.
- **Input Validation**: Minimal input sanitization (e.g., in `src/snek/system/form.py`, forms have placeholders but no regex or length checks enforced). User inputs in RPC calls (e.g., `src/snek/view/rpc.py`) are not validated.
- **Hardcoded Secrets**: Database paths (e.g., 'sqlite:///snek.db' in multiple files), keys (e.g., SESSION_KEY in `src/snek/app.py`), and credentials are hardcoded, posing risks if exposed.
- **Privilege Escalation**: Admin checks are inconsistent (e.g., in `src/snek/service/channel.py`, `src/snek/view/rpc.py`), and some operations (e.g., clearing channels) only check `is_admin` without further validation.
- **WebDAV and SSH**: `src/snek/webdav.py` and `src/snek/sssh.py` handle file access but lack rate limiting or detailed permission checks.
- **Code Quality Issues**:
- **Inconsistent Naming and Style**: Mixed camelCase and snake_case (e.g., `set_typing` vs. `generateUniqueId` in JS). Some classes have similar names (e.g., `DatasetWebSocketView` in research files).
- **Lack of Documentation**: Few docstrings or comments (e.g., no docstrings in `src/snek/service/user.py`, `src/snek/view/channel.py`). Methods are often self-explanatory but lack context.
- **Hardcoded Values**: URLs, paths, and constants are hardcoded (e.g., in `src/snek/static/njet.js`, `src/snek/app.py`).
- **Global State and Side Effects**: Use of global variables (e.g., in `src/snek/system/markdown.py`, `src/snek/view/rpc.py`) and mutable defaults can lead to bugs.
- **Performance Issues**: No visible optimization for large datasets (e.g., in `src/snek/service/channel_message.py`, queries could be inefficient). Process pools in `src/snek/service/channel_message.py` are per-message, potentially wasteful.
- **Error Handling Gaps**: Many methods lack try-except (e.g., in `src/snek/system/docker.py`, `src/snek/system/terminal.py`). Exceptions are sometimes caught but not logged properly.
- **Dependencies**: Imports like `dataset`, `git`, `pymongo` (implied) are not version-pinned, risking compatibility issues.
- **Testing Absence**: No visible unit or integration tests in the codebase.
- **Potential Bugs**:
- In `src/snek/research/serpentarium.py`, `self.setattr(self, "db", self.set)` assigns undefined `self.set`.
- In `src/snek/sync.py`, similar assignment issues.
- In `src/snek/view/rpc.py`, `self.user_uid` property assumes `self.view.session.get("uid")`, but no null checks.
- In `src/snek/system/docker.py`, `ComposeFileManager` uses subprocess without full error handling.
- In `src/snek/service/channel_message.py`, executor pools per UID could lead to resource leaks if not cleaned up.
- In `src/snek/forum.py`, event listeners are added but no removal logic is visible.
- **Maintainability**: Large files (e.g., `src/snek/view/rpc.py` is over 1000 lines) and complex methods (e.g., in `src/snek/app.py`) make refactoring hard. Some code duplication (e.g., WebSocket handling in multiple views).
## Recommendations
- **Fix Syntax Errors Immediately**: Correct `setattr` spellings, remove misplaced `)`, and fix `super()` calls in `src/snek/research/serpentarium.py` and `src/snek/sync.py` to prevent runtime failures.
- **Enhance Security**: Implement parameterized queries, add input validation (e.g., using regex in `src/snek/system/form.py`), enforce authentication in all endpoints, and use environment variables for secrets.
- **Improve Code Quality**: Add docstrings, comments, and consistent naming. Refactor large methods and remove hardcoded values.
- **Add Error Handling and Testing**: Wrap risky operations in try-except, log errors, and introduce unit tests (e.g., using pytest).
- **Address Bugs**: Fix undefined assignments and add null checks.
- **General**: Pin dependencies, review for race conditions in async code, and consider code reviews or linters (e.g., flake8, mypy).
## Grade
B- (Solid foundation with good architecture and features, but critical syntax errors, security vulnerabilities, and quality issues require immediate attention to avoid production risks.)
"SELECT user.uid, user.username,user.color,user.last_ping,user.nick FROM channel_member INNER JOIN user ON user.uid = channel_member.user_uid WHERE channel_uid=:channel_uid AND user.last_ping >= datetime('now', '-3 minutes') ORDER BY last_ping DESC LIMIT 30",
"SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid",
"SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid AND deleted_at IS NULL AND is_banned = 0",
{"channel_uid":channel_uid},
):
yieldmodel["user_uid"]
@@ -56,7 +60,6 @@ class ChannelMemberService(BaseService):
"SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') LEFT JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = NULL AND channel_member2.user_uid = NULL) WHERE channel_member.user_uid=:from_user ",
history_start_filter=f" AND created_at > '{channel['history_start']}'"
results=[]
offset=page*page_size
try:
iftimestamp:
asyncformodelinself.query(
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
f"SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp{history_start_filter} ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
{
"channel_uid":channel_uid,
"page_size":page_size,
@@ -67,7 +186,7 @@ class ChannelMessageService(BaseService):
results.append(model)
elifpage>0:
asyncformodelinself.query(
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size",
f"SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp{history_start_filter} ORDER BY created_at DESC LIMIT :page_size",
{
"channel_uid":channel_uid,
"page_size":page_size,
@@ -78,7 +197,7 @@ class ChannelMessageService(BaseService):
results.append(model)
else:
asyncformodelinself.query(
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
f"SELECT * FROM channel_message WHERE channel_uid=:channel_uid{history_start_filter} ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
{
"channel_uid":channel_uid,
"page_size":page_size,
@@ -87,7 +206,7 @@ class ChannelMessageService(BaseService):
// This JavaScript class defines a custom HTML element for a chat input widget, featuring a text area and an upload button. It handles user input and triggers events for input changes and message submission.
// This code defines a custom HTML element called ChatWindowElement that provides a chat interface within a shadow DOM, handling connection callbacks, displaying messages, and user interactions.
@@ -6,77 +8,77 @@
// The MIT License (MIT)
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// This JavaScript class defines a custom HTML element <fancy-button>, which creates a styled, clickable button element with customizable size, text, and URL redirect functionality.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.