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.
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).
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 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.
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.
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.
- 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
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.
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.
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.
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 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.
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 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 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.