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.
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.
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 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.
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.
Initialize users array, textarea reference, _value, lastUpdateEvent, previousValue, lastChange, and changed properties in class body before constructor, and remove redundant _value initialization from constructor.
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
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.