feat: Implement medium-priority issues #47-#52#129
feat: Implement medium-priority issues #47-#52#129olafkfreund wants to merge 21 commits intocosmic-utils:masterfrom
Conversation
- Remove all .unwrap()/.expect() from production code paths, replacing with proper error handling via Result types and graceful fallbacks - Fix desktop entry injection by sanitizing Name/Exec/WMClass fields - Fix path traversal via app_id by stripping /, \, and .. sequences - Add with_navigation_handler to webview blocking non-http/https URLs - Add with_new_window_req_handler to block unsafe new window requests - Validate URL scheme (http/https only) before loading in webview - Harden icon-installer.sh: fix TOCTOU race, quote variables, add trap - Add inline validation for title/URL fields in editor - Add numeric-only filter with clamping for window size input - Add toast notifications for save/delete via widget::toaster - Add Ctrl+N keyboard shortcut for creating new web app - Restructure app menu with Create new + divider + Settings + About - Add RON file size limit (64KB), WalkDir depth limit (8), icon cap (200) - Bound downloader output buffer to 32KB - Make SVG extension check case-insensitive - Add empty icon search state message - Add CLAUDE.md with project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add favicon auto-detection from URL via Google S2 favicons API - Add search/filter input in header bar for installed web apps - Sort nav bar apps by category then name - Add Ctrl+S keyboard shortcut for saving current web app - Add "New Window" desktop action to generated .desktop files - Add first-run onboarding empty state when no apps installed - Add tooltip on icon picker button for accessibility - Add Nix flake with crane for reproducible builds and dev shell - Replace once_cell dependency with std::sync::LazyLock Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add import/export of web apps via RON files with file picker dialogs - Add app duplication button in editor for installed apps - Add download handler redirecting to XDG download directory - Disable devtools in production webview builds - Add i18n keys for import/export/duplicate functionality Closes #10, #12, #15, #18 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nitization - Restrict url_valid() to http/https schemes only (reject javascript:, file:, data:) - Expand sanitize_desktop_field() to strip all control chars, backslashes, semicolons - Harden favicon download: validate domain chars, add --timeout=10, verify image magic bytes (PNG/JPEG/GIF/ICO), sanitize domain in filename - Fix download handler to deny when dest_path has no valid filename - Add full import validation: sanitize app_id, validate URL/name/icon/category, check profile path prefix, cap file size at 1MB and entries at 500 - Track import success/failure counts with per-app error logging - Handle URL decode errors explicitly instead of silent empty-string fallback - Remove dead ImportAppsResult message variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… runtime deps - Cache installed apps in memory to avoid disk I/O on every search keystroke - Extract save_imported() to launcher.rs for proper separation of concerns - Improve app duplication to preserve all browser settings from original - Replace hardcoded file dialog strings with fl!() i18n macro calls - Fix urlencoding unwrap_or_default() in theme file picker with proper error handling - Add 17 missing i18n keys (English fallbacks) to all 13 non-English locales - Add wget runtime dependency to flake.nix via gappsWrapperArgs PATH prefix - Clean up stale URL in Italian locale file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace eprintln! with tracing::error! for consistent logging (lib.rs, browser.rs, mod.rs) - Replace PathBuf::from_str with PathBuf::from (infallible) in lib.rs and mod.rs - Remove unused std::str::FromStr imports from lib.rs and mod.rs - Remove redundant String::from() wrapper on .to_string() in editor.rs - Remove unnecessary Vec<_> type annotation in lib.rs - Extract filter_numeric() helper to deduplicate window size input filtering in editor.rs - Remove unnecessary intermediate variable in launcher.rs installed_webapps() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…uctions Add comprehensive features list, NixOS/Nix flake build details, system-wide and Home Manager installation examples, keyboard shortcuts, import/export docs, architecture overview, and generic Linux build guide. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntions - Remove redundant .clone() on bool in editor.rs - Use format!() for app_id generation to avoid intermediate allocation - Replace double file open with std::fs::read() in launcher create() - Add path component traversal check (..) on imported profile paths - Validate imported icon paths for directory traversal sequences - Add 2MB size limit on favicon download responses - Add MAX_APP_ID_LEN (128) truncation in sanitize_app_id - Fix icon_pack_installed path accumulation bug (.push without .pop) - Replace all `with pkgs;` with explicit pkgs.* references in flake.nix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security hardening, panic elimination, import/export, UX improvements, code review fixes, and documentation updates. See PR cosmic-utils#127 for details.
- Fix wrapGAppsHook -> wrapGAppsHook3 rename in nixpkgs-unstable - Pin libcosmic to rev 4c4eddb for reproducible Nix builds - Regenerate Cargo.lock with pinned dependency - Fix Rust 2024 edition String-to-&str coercion in ashpd file dialogs - Fix fl!() macro: use flat keys (warning-app-name) not dotted attributes - Fix widget::tooltip() to accept Element via widget::text() wrapper - Fix toast Task<Message> -> Task<Action<Message>> with .map(cosmic::Action::App) - Fix task::message() wrapping with cosmic::action::app() - Fix borrow checker issue with nav iterator in Rust 2024 edition - Fix temporary lifetime in format! macro (owned String vs reference) - Fix add_icon_packs_install_script() error type to be Send + Sync - Remove unused imports (url::Url, ffi::OsStr) - Add flake.lock for reproducible Nix builds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused svg and serde_json dependencies from Cargo.toml - Harden sanitize_app_id with whitelist filter and loop-based .. removal - Validate icon_name/extension in move_icon to prevent path traversal - Fix themes_list index bounds check to prevent panic - Localize icon picker file dialog strings with fl!() macros - Replace .expect() in main.rs with graceful error handling - Handle read_to_end failure in image_handle instead of silently ignoring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add runtime assertion that checks Rust compiler version is >= 1.85.0, which is required for edition = "2024" used in Cargo.toml. The assertion: - Throws a clear error message if nixpkgs provides Rust < 1.85.0 - Includes the detected version in the error message - Suggests updating the nixpkgs input as the fix - Uses minimal, non-invasive approach (no rust-overlay or toolchain changes) This prevents confusing build failures when using older nixpkgs versions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- #22: Move blocking I/O off async runtime with spawn_blocking in find_icon() and image_handle() to prevent UI freezes - #23: Remove redundant installed_webapps() disk scan in AppEditor::from() - #24/#31: Preserve nav selection after save/reload via rebuild_nav_from_cache accepting optional app ID parameter - #25: Add sync I/O doc comments to move_icon() - #26/#34: Add proper error handling in SaveLauncher with toast on failure - #27: Eliminate entity ID staleness by inlining nav reload after save - #28: Remove dead Message::Close no-op variant - #29: Standardize FaviconResult to use Task::perform wrapping convention - #33: Optimize Browser::from_appid() to read single .ron file directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add custom_css and custom_js fields to Browser struct - Inject CSS via <style> element appended to <head> on page load - Inject JS via wry initialization_script on page load - Add CSS/JS text input fields in app editor settings section - Preserve CSS/JS values on app duplicate - Add i18n keys with Fluent-escaped placeholders - JS field includes security warning about full page access Closes #36 Closes #37 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#38, #39, #40) - #35: Per-app permission management (camera, mic, geolocation, notifications) with JavaScript-based enforcement in webview - #38: Custom user agent string with Default/Mobile/Custom presets - #39: Forward web notifications to COSMIC desktop via notify-rust IPC - #40: Website data management — clear cookies/cache/storage per app Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#41: Expanded desktop actions — New Window, New Private Window (--private flag), Open in Browser (xdg-open). Added `private` field to WebviewArgs with --private CLI flag support in webview binary. #42: URL scheme handlers — Added url_schemes field to Browser struct, comma-separated input in editor UI, parsed and validated on save. Added i18n keys (url-schemes, url-schemes-placeholder). #43: Media session integration — Auto-wires play/pause/seek handlers to first video/audio element via initialization script. Reports media state changes over IPC. MutationObserver re-wires on DOM changes. #44: Notification badge detection — Monitors document.title for [N]/(N) patterns and intercepts Badging API (setAppBadge/clearAppBadge). Reports badge count changes over IPC. #45: Site title auto-detection — fetch_site_title() fetches page HTML via wget and extracts <title> tag. Triggered alongside favicon download when title field is empty. Includes html_decode_basic() for entities. #46: StartupWMClass fix — WM_CLASS is set via gtk::glib::set_program_name() before window creation, which GTK uses as the X11 WM_CLASS res_name. Matches StartupWMClass in generated .desktop entries. Also refactored IPC handler to be unconditional (handles notifications, media state, and badge count), and reformatted code per clippy/rustfmt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The COSMIC trademark policy reserves the cosmic- prefix for official System76 packages. Third-party projects should use cosmic-ext- instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rsion check - Replace flake-utils with nixpkgs.lib.genAttrs scoped to Linux only - Replace fragile _ binding version check with idiomatic assert - Add overlays.default output for NixOS system integration - Remove redundant rustfmt/clippy from devShell (already in toolchain) - Update CLAUDE.md to document flake.nix existence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- #49: Split editor into Basic/Advanced collapsible sections - #51: Add keyboard shortcuts (Ctrl+F/E/I/L/D) for search, export, import, launch, and duplicate - #52: Quick-actions toolbar (Run, Duplicate, Open in Browser, Delete) for installed apps above the editor - #50: Running app status indicators via /proc scanning with 5s polling, shown as filled circle prefix in nav bar - #47: Grid/card view toggle with icon cards, persisted in config (v2) - #48: App preview thumbnails via thum.io with 24h cache and auto-fetch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Implements a batch of medium-priority UX/features for Quick Web Apps, including editor UX improvements, app management actions, view mode persistence, thumbnails, keyboard shortcuts, and status indicators; also adds import/export and various hardening changes.
Changes:
- Add Basic/Advanced editor sections, quick-actions toolbar, duplication, and app data clearing.
- Add keyboard shortcuts, search, grid/list view toggle (persisted via config v2), and running-app indicators via
/procpolling. - Add thumbnail previews (thum.io) + favicon/title fetching, plus import/export of app configs and Nix flake packaging/docs updates.
Reviewed changes
Copilot reviewed 23 out of 31 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/localize.rs | Switch localization loader to std::sync::LazyLock. |
| src/lib.rs | Config v2 bump; add favicon/title/thumbnail download + caching helpers; add /proc running scan; improve error handling and blocking I/O usage. |
| src/launcher.rs | Harden DB reads; sanitize desktop entry fields; add desktop actions; add import/export implementation. |
| src/browser.rs | Add app-id sanitization + new per-app settings (UA, permissions, CSS/JS, schemes); change config load path. |
| src/bin/webview.rs | Add URL-scheme safety checks, permission/UA handling, notification forwarding, and custom CSS/JS injection. |
| src/bin/dev-heppen-webapps/pages/mod.rs | Add shortcuts, search, grid/list view, running indicators polling, quick-actions toolbar, import/export UI, toasts. |
| src/bin/dev-heppen-webapps/pages/iconpicker.rs | Improve file chooser error handling + empty search results UX. |
| src/bin/dev-heppen-webapps/pages/editor.rs | Basic/Advanced sections; favicon + title fetch; duplication; thumbnail loading; validation hints. |
| src/bin/dev-heppen-webapps/main.rs | Avoid panic on tracing subscriber init failure. |
| src/bin/dev-heppen-webapps/config.rs | Add persisted ViewMode and bump config version to 2. |
| resources/scripts/icon-installer.sh | Safer temp file handling + cleanup via trap; quote paths. |
| i18n/*/webapps.ftl | Add new strings for import/export, toasts, advanced settings, thumbnails, etc. |
| flake.nix / flake.lock | Add Nix flake build/devshell/checks; wrap PATH with wget. |
| README.md | Expand feature/docs; add Nix instructions and architecture section. |
| Cargo.toml / Cargo.lock | Add deps (notify-rust), remove once_cell/svg, pin libcosmic rev. |
| CLAUDE.md | Repository guide / architecture notes for Claude Code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if sanitized.len() > MAX_APP_ID_LEN { | ||
| sanitized[..MAX_APP_ID_LEN].to_string() |
There was a problem hiding this comment.
sanitize_app_id truncates with sanitized[..MAX_APP_ID_LEN], which slices by byte index and can panic if the sanitized ID contains non-ASCII alphanumeric characters (UTF-8 multibyte). Consider either restricting to ASCII (is_ascii_alphanumeric) or truncating by chars().take(MAX_APP_ID_LEN) to guarantee valid boundaries.
| if sanitized.len() > MAX_APP_ID_LEN { | |
| sanitized[..MAX_APP_ID_LEN].to_string() | |
| if sanitized.chars().count() > MAX_APP_ID_LEN { | |
| sanitized.chars().take(MAX_APP_ID_LEN).collect() |
| let content = std::fs::read_to_string(&db_path).ok()?; | ||
|
|
||
| // Same 64KB safety limit used in launcher::installed_webapps() | ||
| const MAX_RON_SIZE: usize = 64 * 1024; | ||
| if content.len() > MAX_RON_SIZE { | ||
| tracing::warn!("RON file too large: {}", db_path.display()); | ||
| return None; | ||
| } | ||
|
|
There was a problem hiding this comment.
Browser::from_appid reads the entire RON file into memory before enforcing the 64KB limit. A large file in the database directory could still cause excessive memory usage/slowdown. Prefer checking metadata.len() (or reading only up to the limit) before read_to_string.
| let content = std::fs::read_to_string(&db_path).ok()?; | |
| // Same 64KB safety limit used in launcher::installed_webapps() | |
| const MAX_RON_SIZE: usize = 64 * 1024; | |
| if content.len() > MAX_RON_SIZE { | |
| tracing::warn!("RON file too large: {}", db_path.display()); | |
| return None; | |
| } | |
| // Same 64KB safety limit used in launcher::installed_webapps() | |
| const MAX_RON_SIZE: usize = 64 * 1024; | |
| // Check file size via metadata before reading to avoid loading overly large files. | |
| let metadata = std::fs::metadata(&db_path).ok()?; | |
| if metadata.len() > MAX_RON_SIZE as u64 { | |
| tracing::warn!("RON file too large: {}", db_path.display()); | |
| return None; | |
| } | |
| let content = std::fs::read_to_string(&db_path).ok()?; |
| license = pkgs.lib.licenses.gpl3Only; | ||
| maintainers = []; | ||
| platforms = pkgs.lib.platforms.linux; | ||
| mainProgram = "dev-heppen-webapps"; |
There was a problem hiding this comment.
The package installs the main binary as dev.heppen.webapps (see justfile and apps.default.program), but meta.mainProgram is set to dev-heppen-webapps. This mismatch can affect tooling that relies on mainProgram. Consider setting mainProgram to dev.heppen.webapps.
| mainProgram = "dev-heppen-webapps"; | |
| mainProgram = "dev.heppen.webapps"; |
| #### Quick run (no installation) | ||
|
|
||
| ```bash | ||
| nix run github:olafkfreund/cosmic-ext-web-apps | ||
| ``` | ||
|
|
||
| #### Build from source | ||
|
|
||
| ```bash | ||
| git clone https://github.com/olafkfreund/cosmic-ext-web-apps.git | ||
| cd cosmic-ext-web-apps | ||
| nix build | ||
| ./result/bin/dev.heppen.webapps | ||
| ``` |
There was a problem hiding this comment.
The Nix instructions reference github:olafkfreund/cosmic-ext-web-apps, which doesn’t match this repository (cosmic-utils/web-apps). This will mislead users trying to use the flake from this repo. Consider updating the URLs and directory names to point at the current repository / flake output names.
- #53: Content blocking (ads/trackers) via DOM removal - #54: HTTP/HTTPS proxy support via environment variables - #55: Configurable page zoom level via CSS zoom - #56: Session restore — saves/restores last visited URL - #57: Usage statistics — launch count and last launched timestamp - #58: Bulk operations — select, delete, export multiple apps - #59: Minimize to background on window close - #60: Block third-party cookies via JS override - #61: Block WebRTC IP leak by disabling RTCPeerConnection - #62: Auto dark mode via CSS filter inversion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
/procscanning every 5 seconds, displayed as a filled circle (●) prefix in the nav bar.Test plan
just runand verify Basic/Advanced editor sections toggle correctly🤖 Generated with Claude Code