From be619f7953344c952b44594e01e2fa9e2df75fde Mon Sep 17 00:00:00 2001 From: Cole Mei Date: Tue, 18 Aug 2026 16:19:15 +0800 Subject: [PATCH] feat: default history views to a 7-day window The 90-day retention in record_at only prunes on write, so an install that hasn't changed an association in weeks kept showing months-old rows. Apply the window on read instead, via history::recent_within. Default is 7 days across all three surfaces (Profiles HISTORY panel, menu-bar popover, `openwith history`), settable in Settings -> Behavior and via --days/--all on the CLI. Nothing is deleted: the ledger keeps its 90-day / 500-event cap, the panel offers a session-only "Show all", and undo still reads the unwindowed ledger so a hidden event stays revertible. --- CLAUDE.md | 8 +- README.md | 3 +- crates/openwith-cli/src/cli.rs | 8 +- crates/openwith-cli/src/commands/history.rs | 15 +++- crates/openwith-cli/src/main.rs | 9 ++- crates/openwith-core/src/history.rs | 79 ++++++++++++++++++- crates/openwith-gui/src-tauri/src/commands.rs | 13 ++- crates/openwith-gui/src/api.ts | 8 +- crates/openwith-gui/src/app.ts | 60 +++++++++++++- crates/openwith-gui/src/menubar.ts | 14 +++- crates/openwith-gui/src/state.ts | 11 +++ crates/openwith-gui/src/styles.css | 25 ++++++ 12 files changed, 230 insertions(+), 23 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9f1703f..47dbe51 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ cargo run -p openwith-cli -- set -s http Firefox # set default browser cargo run -p openwith-cli -- export -o out.toml # export associations to TOML cargo run -p openwith-cli -- import --dry-run out.toml # preview an import cargo run -p openwith-cli -- import out.toml # import associations from TOML -cargo run -p openwith-cli -- history # recent changes from CLI + GUI (--json) +cargo run -p openwith-cli -- history # recent changes, last 7 days (--days N, --all, --json) cargo run -p openwith-cli -- undo # revert the most recent change cargo check # quick compile check cargo test # run tests @@ -62,7 +62,7 @@ crates/ set.rs -- `openwith set ` with name/bundle-ID resolution export.rs -- `openwith export` dump associations + schemes to TOML import.rs -- `openwith import` apply TOML (idempotent, `--dry-run`) - history.rs -- `openwith history` list recent events (relative dates, --json) + history.rs -- `openwith history` list recent events (relative dates, --days/--all, --json) undo.rs -- `openwith undo` revert last set (drift check, --force) tui.rs -- ratatui TUI: Extensions + Apps tabs, loading screen, AppPicker + Help openwith-gui/ -- Tauri v2 GUI ("OpenWith.app") @@ -92,7 +92,8 @@ crates/ - Loading screen enters TUI alternate screen immediately, shows ASCII logo + spinner while scanning in background. - Export/import uses serde + toml crate with `BTreeMap` for sorted, human-readable TOML; import validates apps exist and skips associations already set correctly. - GUI: single `get_snapshot` command returns apps + associations (with sibling-UTI conflict data) + contested schemes in one call; the frontend is a plain render-to-innerHTML loop with `data-action` event delegation, no framework. Versions are lockstep: `tauri.conf.json` omits `version` so the app version comes from `workspace.package` in the root Cargo.toml. -- `openwith-core::history` is the shared change log (capped at 500 events, best-effort writes that never fail the triggering change). CLI, GUI, and core import all record into it; the GUI Profiles panel shows export/import events, the menu-bar popover shows set events with per-entry Undo, and `openwith history`/`openwith undo` read the same file. +- `openwith-core::history` is the shared change log (capped at 500 events / 90 days, best-effort writes that never fail the triggering change). CLI, GUI, and core import all record into it; the GUI Profiles panel shows export/import events, the menu-bar popover shows set events with per-entry Undo, and `openwith history`/`openwith undo` read the same file. +- History retention is a **display window, not deletion**. The 90-day/500-event prune in `record_at` is only a ledger backstop and runs on *write*, so a dormant install would otherwise show months-old rows forever — the window is therefore applied on **read**, via `history::recent_within`. Default is 7 days (`DEFAULT_WINDOW_DAYS`), settable in Settings → Behavior → "Show history for" (1 week / 1 month / All — the ledger's own 90-day cap makes a "3 months" option redundant; persisted as `historyWindowDays` in localStorage and shared by both windows) and via `openwith history --days N` / `--all`. The Profiles HISTORY panel head shows the active window plus a session-only "Show all" toggle (`state.historyShowAll`, deliberately not persisted). `undo_change` and `openwith undo` keep reading the *unwindowed* ledger (`history::recent`) so a hidden event stays revertible. - The GUI is two windows off one Vite bundle: `main` and a hidden transparent `menubar` popover (requires `macOSPrivateApi: true`). The popover hides on blur and is toggled by the tray icon or a configurable global shortcut (default ⌥⌘O; `set_toggle_shortcut` swaps the registration at runtime, the saved accelerator is re-applied at bootstrap). A **Pin** button suspends hide-on-blur for one showing (backend `PopoverPinned` AtomicBool, reset on every toggle) so a file can be dragged in from Finder — without it the click into Finder blurs and hides the panel. Focus events are unreliable for the transparent panel, so the backend emits `popover-shown` on every open and the popover refreshes from that, not just from focus. A backend `AppsCache` (refreshed by `get_snapshot`) keeps popover lookups instant. - App icons come from the 2026-07 logo renders in `artifacts/` (`logo-mono-glyph.png`, `logo-icon-dark.png`, `logo-icon-light.png` — AI renders with **no alpha**, so masking is scripted, not manual): light → 1024 master → `tauri icon` set; dark → `icons/icon-dark.png`; mono glyph → `icons/tray-template.png` (64×44 black+alpha template, glyph ≈34px tall). The Dock icon follows the app's *resolved* appearance at runtime — `set_dock_icon_dark` swaps `NSApplication.applicationIconImage` between `icon.png`/`icon-dark.png` (macOS only re-renders bundle icons for the system appearance), invoked from `theme.ts` on every theme change. `icon.icns` is **hand-built**: the ≤64px slots use a legibility variant (header dots inpainted away, no shadow, larger glyph), so rerunning `tauri icon` clobbers it. All of these regenerate with `uv run scripts/gen-icons.py` (run it *after* any `tauri icon` invocation; it prints the `tauri icon` command for the PNG set). - GUI settings live in localStorage (`openwith.settings`). The Settings pane mirrors the design prototype's full layout; controls whose feature ships in a later 0.5.x phase (launch at login, menu bar) render disabled with an "arrives in v0.5.1" note rather than as silently-dead toggles. @@ -174,6 +175,7 @@ Run against the built .app (not just `tauri dev`) before tagging any release wit - [ ] With the CLI upgraded via brew while the app runs: close and reopen Settings — the Command Line panel shows the new version without an app relaunch - [ ] Profiles: export; import via choose AND drag-drop; dry-run preview; apply; dismiss - [ ] History panel scrolls at 50 entries and updates after changes +- [ ] History window: default shows only the last 7 days in both the Profiles panel and the popover; "Show all" reveals older rows and toggles back; switching the Settings segment (1 week/1 month/All) refetches both surfaces; the popover follows a change made in the main window without a relaunch - [ ] Check Now (updates) reports a sensible result on both channels - [ ] README screenshots (from the design prototype, `artifacts/gui-*.png`) still match the shipped UI — recapture if the UI changed diff --git a/README.md b/README.md index 2d00696..9788615 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,8 @@ openwith set md Typora # Set Typora as default for .md openwith set md abnerworks.Typora # Bundle IDs work too openwith current -s http # Show the default browser openwith set -s http Firefox # Set the default browser -openwith history # Recent changes (recorded by CLI and GUI alike) +openwith history # Recent changes, last 7 days (recorded by CLI and GUI alike) +openwith history --all # Everything still retained (90 days / 500 events) openwith undo # Revert the most recent change ``` diff --git a/crates/openwith-cli/src/cli.rs b/crates/openwith-cli/src/cli.rs index 4e2e6bf..0631106 100644 --- a/crates/openwith-cli/src/cli.rs +++ b/crates/openwith-cli/src/cli.rs @@ -21,7 +21,7 @@ MANAGE openwith set Set the default app (name or bundle ID) openwith current -s http Show the handler for a URL scheme openwith set -s http Set the handler for a URL scheme - openwith history Show recent changes (--json for scripts) + openwith history Show recent changes, last 7 days (--all, --json) openwith undo Revert the most recent change CONFIG @@ -106,6 +106,12 @@ pub enum Commands { /// Maximum number of events to show #[arg(short = 'n', long, default_value_t = 20)] limit: usize, + /// Only show events from the last N days + #[arg(short = 'd', long, default_value_t = openwith_core::history::DEFAULT_WINDOW_DAYS)] + days: u64, + /// Show every retained event, ignoring --days + #[arg(long, conflicts_with = "days")] + all: bool, /// Print JSON #[arg(long)] json: bool, diff --git a/crates/openwith-cli/src/commands/history.rs b/crates/openwith-cli/src/commands/history.rs index 7aa3306..376a5ea 100644 --- a/crates/openwith-cli/src/commands/history.rs +++ b/crates/openwith-cli/src/commands/history.rs @@ -3,8 +3,9 @@ use anyhow::Result; use openwith_core::history::{self, HistoryEvent}; use openwith_core::scanner; -pub fn run(limit: usize, json: bool) -> Result<()> { - let events = history::recent(limit)?; +/// `window_days` bounds how far back events are shown; `None` is `--all`. +pub fn run(limit: usize, window_days: Option, json: bool) -> Result<()> { + let events = history::recent_within(limit, window_days)?; if json { let out: Vec = events @@ -28,7 +29,15 @@ pub fn run(limit: usize, json: bool) -> Result<()> { } if events.is_empty() { - println!("No history yet — changes made by the CLI or GUI will appear here."); + match window_days { + // The ledger may still hold older events — say so rather than + // implying nothing was ever recorded. + Some(days) => println!( + "No changes in the last {days} day{} — use --all to see everything retained.", + if days == 1 { "" } else { "s" } + ), + None => println!("No history yet — changes made by the CLI or GUI will appear here."), + } return Ok(()); } diff --git a/crates/openwith-cli/src/main.rs b/crates/openwith-cli/src/main.rs index 76bc117..c7bfa6e 100644 --- a/crates/openwith-cli/src/main.rs +++ b/crates/openwith-cli/src/main.rs @@ -19,8 +19,13 @@ fn main() -> Result<()> { Some(cli::Commands::Apps) => { commands::tui::run(commands::tui::InitialView::Apps)?; } - Some(cli::Commands::History { limit, json }) => { - commands::history::run(limit, json)?; + Some(cli::Commands::History { + limit, + days, + all, + json, + }) => { + commands::history::run(limit, if all { None } else { Some(days) }, json)?; } Some(cli::Commands::Undo { force }) => { commands::undo::run(force)?; diff --git a/crates/openwith-core/src/history.rs b/crates/openwith-core/src/history.rs index 03ed5c3..bf3e3be 100644 --- a/crates/openwith-core/src/history.rs +++ b/crates/openwith-core/src/history.rs @@ -13,9 +13,20 @@ use serde::{Deserialize, Serialize}; /// Keep the log bounded; older events fall off the front. const MAX_EVENTS: usize = 500; -/// Events older than this are pruned on every write. +/// Events older than this are pruned on every write. This is the ledger's +/// hard ceiling, not the default view: surfaces apply their own, much shorter +/// display window (see `DEFAULT_WINDOW_DAYS`) on read. const MAX_AGE_SECS: u64 = 90 * 24 * 60 * 60; +/// Default display window, in days. Changing a default is a "did I just break +/// my PDFs?" action — the useful lookback is days, not months, so every +/// surface (Profiles panel, popover, `openwith history`) shows this much +/// unless the user widens it. +pub const DEFAULT_WINDOW_DAYS: u64 = 7; + +/// Seconds in a day, for turning a window in days into a cutoff. +pub const DAY_SECS: u64 = 24 * 60 * 60; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub struct HistoryEvent { /// "set" | "set_scheme" | "export" | "import" @@ -75,10 +86,24 @@ pub fn record(event: HistoryEvent) -> Result<()> { } /// Newest-first slice of the default history file. Missing file → empty. +/// +/// Unwindowed: this is the ledger view, used by undo lookups that must still +/// find an event the display window has scrolled past. Display surfaces want +/// [`recent_within`] instead. pub fn recent(limit: usize) -> Result> { recent_at(&history_path()?, limit) } +/// Newest-first slice restricted to the last `window_days` days. `None` keeps +/// everything the ledger still holds. +/// +/// The window has to be enforced here rather than only in `record_at`: pruning +/// happens on write, so an install that hasn't changed anything in months +/// would otherwise keep showing months-old rows. +pub fn recent_within(limit: usize, window_days: Option) -> Result> { + recent_within_at(&history_path()?, limit, window_days) +} + fn load(path: &Path) -> Vec { // A missing or corrupt log starts fresh rather than blocking changes. std::fs::read_to_string(path) @@ -104,7 +129,19 @@ pub fn record_at(path: &Path, event: HistoryEvent) -> Result<()> { } pub fn recent_at(path: &Path, limit: usize) -> Result> { + recent_within_at(path, limit, None) +} + +pub fn recent_within_at( + path: &Path, + limit: usize, + window_days: Option, +) -> Result> { let mut events = load(path); + if let Some(days) = window_days { + let cutoff = now_secs().saturating_sub(days.saturating_mul(DAY_SECS)); + events.retain(|e| e.timestamp >= cutoff); + } events.reverse(); events.truncate(limit); Ok(events) @@ -269,6 +306,46 @@ mod tests { std::fs::remove_file(&path).unwrap(); } + #[test] + fn display_window_filters_on_read() { + let path = temp_log("window"); + let _ = std::fs::remove_file(&path); + + // Seeded directly: a dormant install never calls record_at, which is + // exactly the case the read-side window has to cover. + let old = event("set", ".old", now_secs() - 30 * DAY_SECS); + let fresh = event("set", ".fresh", now_secs() - 2 * DAY_SECS); + std::fs::write(&path, serde_json::to_string(&vec![old, fresh]).unwrap()).unwrap(); + + let windowed = recent_within_at(&path, 10, Some(DEFAULT_WINDOW_DAYS)).unwrap(); + assert_eq!(windowed.len(), 1); + assert_eq!(windowed[0].key, ".fresh"); + + // None keeps everything the ledger still holds... + assert_eq!(recent_within_at(&path, 10, None).unwrap().len(), 2); + // ...and a wide enough window is equivalent. + assert_eq!(recent_within_at(&path, 10, Some(90)).unwrap().len(), 2); + // The unwindowed ledger view (undo lookups) still sees the old event. + assert_eq!(recent_at(&path, 10).unwrap().len(), 2); + + std::fs::remove_file(&path).unwrap(); + } + + #[test] + fn window_does_not_delete_anything() { + let path = temp_log("window-nondestructive"); + let _ = std::fs::remove_file(&path); + + let old = event("set", ".old", now_secs() - 30 * DAY_SECS); + std::fs::write(&path, serde_json::to_string(&vec![old]).unwrap()).unwrap(); + + assert!(recent_within_at(&path, 10, Some(7)).unwrap().is_empty()); + // Reading through a narrow window must not rewrite the file. + assert_eq!(recent_at(&path, 10).unwrap().len(), 1); + + std::fs::remove_file(&path).unwrap(); + } + #[test] fn old_events_are_pruned_on_write() { let path = temp_log("prune"); diff --git a/crates/openwith-gui/src-tauri/src/commands.rs b/crates/openwith-gui/src-tauri/src/commands.rs index 1a659d5..9a73b2b 100644 --- a/crates/openwith-gui/src-tauri/src/commands.rs +++ b/crates/openwith-gui/src-tauri/src/commands.rs @@ -220,13 +220,18 @@ pub fn get_ext_picker( /// Recent set events for the popover's Recent Changes list, names resolved. /// Undo-stack view: undone changes and the reverts themselves are hidden. +/// +/// `window_days` is the caller's display window (`None` = everything retained); +/// it is applied before the undo-stack filter so a quiet week shows an empty +/// panel rather than months-old rows. #[tauri::command] pub fn get_recent_changes( limit: usize, + window_days: Option, cache: State<'_, AppsCache>, ) -> Result, String> { let apps = cached_apps(&cache)?; - let events = history::recent(100).map_err(|e| e.to_string())?; + let events = history::recent_within(100, window_days).map_err(|e| e.to_string())?; Ok(events .into_iter() .filter(|e| matches!(e.kind.as_str(), "set" | "set_scheme") && !e.undone && !e.is_undo) @@ -389,14 +394,16 @@ pub struct HistoryEventDto { pub is_undo: bool, } -/// Full ledger for the Profiles HISTORY panel, bundle IDs resolved to names. +/// Ledger for the Profiles HISTORY panel, bundle IDs resolved to names, +/// restricted to `window_days` (`None` = everything retained). #[tauri::command] pub fn get_history( limit: usize, + window_days: Option, cache: State<'_, AppsCache>, ) -> Result, String> { let apps = cached_apps(&cache)?; - let events = history::recent(limit).map_err(|e| e.to_string())?; + let events = history::recent_within(limit, window_days).map_err(|e| e.to_string())?; Ok(events .into_iter() .map(|e| HistoryEventDto { diff --git a/crates/openwith-gui/src/api.ts b/crates/openwith-gui/src/api.ts index 297976a..3620e84 100644 --- a/crates/openwith-gui/src/api.ts +++ b/crates/openwith-gui/src/api.ts @@ -106,14 +106,14 @@ export const api = { invoke("export_toml", { path }), importToml: (path: string, dryRun: boolean) => invoke("import_toml", { path, dryRun }), - getHistory: (limit: number) => - invoke("get_history", { limit }), + getHistory: (limit: number, windowDays: number | null) => + invoke("get_history", { limit, windowDays }), searchExtensions: (query: string) => invoke("search_extensions", { query }), getExtPicker: (ext: string) => invoke("get_ext_picker", { ext }), - getRecentChanges: (limit: number) => - invoke("get_recent_changes", { limit }), + getRecentChanges: (limit: number, windowDays: number | null) => + invoke("get_recent_changes", { limit, windowDays }), undoChange: (kind: string, key: string, timestamp: number) => invoke("undo_change", { kind, key, timestamp }), showMainWindow: () => invoke("show_main_window"), diff --git a/crates/openwith-gui/src/app.ts b/crates/openwith-gui/src/app.ts index d3df8d7..f794aa4 100644 --- a/crates/openwith-gui/src/app.ts +++ b/crates/openwith-gui/src/app.ts @@ -259,15 +259,48 @@ function historyRow(e: import("./api").HistoryEventDto): string { `; } +/** The window actually in force: the persisted setting, unless the panel's + * session-only "Show all" override is on. `null` means no filter. */ +export function effectiveHistoryWindow(): number | null { + return state.historyShowAll ? null : state.settings.historyWindowDays; +} + +/** "Last 7 days" / "Last 30 days" / "All changes" — used in the panel head. */ +function historyWindowLabel(days: number | null): string { + if (days === null) return "All changes"; + if (days === 1) return "Last 24 hours"; + return `Last ${days} days`; +} + function renderHistory(): string { + const windowDays = effectiveHistoryWindow(); + const empty = + windowDays === null + ? "Changes, exports, and imports will appear here." + : `No changes in the ${historyWindowLabel(windowDays).toLowerCase()} — older changes are still kept.`; const rows = state.history.length > 0 ? state.history.map(historyRow).join("") - : `
Changes, exports, and imports will appear here.
`; + : `
${escapeHtml(empty)}
`; + + // Older events are hidden, never deleted — offer the way back to them + // whenever the setting is narrowing the view. + const canWiden = state.settings.historyWindowDays !== null; + const widen = canWiden + ? `` + : ""; return `
-
HISTORY
+
+ HISTORY + ${escapeHtml(historyWindowLabel(windowDays))} + ${widen} +
${rows}
`; } @@ -421,6 +454,10 @@ function renderSettings(): string { ${toggleRow("warnUtiConflicts", s.warnUtiConflicts, "Warn on UTI conflicts", "Flag changes that affect sibling extensions like .env / .txt")} ${toggleRow("showBundleIds", s.showBundleIds, "Show bundle IDs", "Display raw bundle identifiers in lists")} ${toggleRow("relaunchFinder", s.relaunchFinder, "Relaunch Finder after changes", "Clears stale icon caches — closes Finder windows")} +
+ Show history forHow far back the History panel and menu bar look — older changes are kept, just hidden + ${segmented("set-history-window", "days", [{ key: "7", label: "1 week" }, { key: "30", label: "1 month" }, { key: "all", label: "All" }], s.historyWindowDays === null ? "all" : String(s.historyWindowDays))} +
UPDATES
@@ -609,7 +646,7 @@ function render() { function refreshHistory() { api - .getHistory(50) + .getHistory(50, effectiveHistoryWindow()) .then((events) => { state.history = events; render(); @@ -1048,6 +1085,23 @@ root.addEventListener("click", (e) => { applyTheme(); render(); break; + case "set-history-window": { + const raw = target.dataset.days; + state.settings.historyWindowDays = raw === "all" ? null : Number(raw); + // An explicit choice supersedes the panel's one-off override. + state.historyShowAll = false; + saveSettings(); + // Refetch rather than filter in place: widening needs rows we never + // asked the backend for. refreshHistory() re-renders on completion. + refreshHistory(); + render(); + break; + } + case "toggle-history-all": + state.historyShowAll = !state.historyShowAll; + refreshHistory(); + render(); + break; case "set-channel": state.settings.updateChannel = target.dataset.channel as "stable" | "beta"; saveSettings(); diff --git a/crates/openwith-gui/src/menubar.ts b/crates/openwith-gui/src/menubar.ts index 7687c56..67e1c67 100644 --- a/crates/openwith-gui/src/menubar.ts +++ b/crates/openwith-gui/src/menubar.ts @@ -77,7 +77,12 @@ function renderMatches(): string { function renderRecent(): string { if (state.recent.length === 0) { - return `
No changes recorded yet.
`; + const days = shared.settings.historyWindowDays; + const empty = + days === null + ? "No changes recorded yet." + : `Nothing in the last ${days} days.`; + return `
${escapeHtml(empty)}
`; } return state.recent .map((e, i) => { @@ -168,7 +173,9 @@ async function refreshMatches() { async function refreshRecent() { try { - state.recent = await api.getRecentChanges(4); + // Same window as the main window's HISTORY panel — reloadSettings() on + // `storage` events keeps this in step when the setting changes there. + state.recent = await api.getRecentChanges(4, shared.settings.historyWindowDays); } catch { state.recent = []; } @@ -280,8 +287,11 @@ void popoverWindow.listen("popover-shown", () => { }); // Settings changed in the main window (shortcut, bundle IDs) reach us here. +// The history window is one of them, and widening it needs rows we never +// fetched — so refetch rather than just re-render. window.addEventListener("storage", () => { reloadSettings(); + void refreshRecent(); render(); }); diff --git a/crates/openwith-gui/src/state.ts b/crates/openwith-gui/src/state.ts index d5b057f..6cc95e2 100644 --- a/crates/openwith-gui/src/state.ts +++ b/crates/openwith-gui/src/state.ts @@ -56,6 +56,11 @@ export interface SettingsState { openOnTab: Tab; /** Global popover toggle, in Tauri accelerator form (e.g. "alt+cmd+o"). */ toggleShortcut: string; + /** How far back the Profiles HISTORY panel and the popover's Recent Changes + * look, in days. `null` shows everything the ledger still retains (capped at + * 90 days / 500 events by the core). Purely a display window — nothing is + * deleted, and `openwith undo` still reaches past it. */ + historyWindowDays: number | null; } const SETTINGS_KEY = "openwith.settings"; @@ -73,6 +78,7 @@ const DEFAULT_SETTINGS: SettingsState = { updateChannel: "stable", openOnTab: "extensions", toggleShortcut: "alt+cmd+o", + historyWindowDays: 7, }; function loadSettings(): SettingsState { @@ -132,6 +138,10 @@ export interface State { importPending: ImportPending | null; windowDragOver: boolean; history: HistoryEventDto[]; + /** Session-only override of `settings.historyWindowDays`, set by the HISTORY + * panel's "Show all" button. Not persisted — widening the window is a + * one-off "where did that change go?" action, not a preference. */ + historyShowAll: boolean; settings: SettingsState; @@ -161,6 +171,7 @@ export const state: State = { importPending: null, windowDragOver: false, history: [], + historyShowAll: false, settings: initialSettings, diff --git a/crates/openwith-gui/src/styles.css b/crates/openwith-gui/src/styles.css index 14d65cb..a0e9232 100644 --- a/crates/openwith-gui/src/styles.css +++ b/crates/openwith-gui/src/styles.css @@ -764,6 +764,30 @@ body { color: var(--text); } +/* Current history window, pushed to the right of the HISTORY label. Normal + weight and no tracking so it reads as state, not as a second heading. */ +.panel-block-note { + margin-left: auto; + font-weight: 500; + letter-spacing: 0; + color: var(--text-faint); +} + +.panel-block-action { + font-size: 11px; + font-weight: 600; + color: var(--accent); + cursor: default; + background: none; + border: none; + padding: 0; + font-family: inherit; +} + +.panel-block-action:hover { + opacity: 0.75; +} + .preview-body { padding: 6px 14px 10px; font-family: ui-monospace, Menlo, monospace; @@ -947,6 +971,7 @@ body { .segmented .option { font-size: 11px; + white-space: nowrap; padding: 4px 10px; border-radius: 5px; color: var(--text-faint);