From aaedd82c4d4eeca2b07581c0011e4486e7c6cbbc Mon Sep 17 00:00:00 2001 From: Robin Ebers Date: Sat, 7 Feb 2026 12:00:32 +0400 Subject: [PATCH 1/3] feat(mock-plugin): enhance mock plugin output and update progress labels - Updated the mock plugin's JSON configuration to include new progress labels for various pacing statuses. - Modified the App component to generate mock output with detailed progress metrics for testing purposes. - Improved CSS variables for color consistency across themes, including new definitions for green, yellow, and red colors. - Adjusted side navigation styles to ensure proper contrast in light and dark modes. - Enhanced progress component styling for better visual representation in dark mode. --- plugins/mock/plugin.json | 22 +++++++++++++----- src-tauri/Cargo.toml | 2 +- src/App.tsx | 39 ++++++++++++++++++++++++++++++++ src/components/side-nav.test.tsx | 6 ++--- src/components/side-nav.tsx | 4 ++-- src/components/ui/progress.tsx | 2 +- src/index.css | 30 +++++++++++++++++------- 7 files changed, 84 insertions(+), 21 deletions(-) diff --git a/plugins/mock/plugin.json b/plugins/mock/plugin.json index dfc906e5..aab922f0 100644 --- a/plugins/mock/plugin.json +++ b/plugins/mock/plugin.json @@ -7,11 +7,21 @@ "icon": "icon.svg", "brandColor": "#EF4444", "lines": [ - { "type": "text", "label": "Config", "scope": "overview" }, - { "type": "badge", "label": "Case", "scope": "overview" }, - { "type": "progress", "label": "Percent", "scope": "overview", "primaryOrder": 1 }, - { "type": "progress", "label": "Dollars", "scope": "detail" }, - { "type": "text", "label": "Now", "scope": "detail" }, - { "type": "badge", "label": "Warning", "scope": "detail" } + { "type": "progress", "label": "Ahead pace", "scope": "overview", "primaryOrder": 1 }, + { "type": "progress", "label": "On Track pace", "scope": "overview", "primaryOrder": 2 }, + { "type": "progress", "label": "Behind pace", "scope": "overview", "primaryOrder": 3 }, + { "type": "progress", "label": "Empty bar", "scope": "overview", "primaryOrder": 4 }, + { "type": "progress", "label": "Exactly full", "scope": "overview", "primaryOrder": 5 }, + { "type": "progress", "label": "Over limit!", "scope": "overview", "primaryOrder": 6 }, + { "type": "progress", "label": "Huge numbers", "scope": "overview", "primaryOrder": 7 }, + { "type": "progress", "label": "Tiny sliver", "scope": "overview", "primaryOrder": 8 }, + { "type": "progress", "label": "Almost full", "scope": "overview", "primaryOrder": 9 }, + { "type": "progress", "label": "Expired reset", "scope": "overview", "primaryOrder": 10 }, + { "type": "text", "label": "Status", "scope": "overview" }, + { "type": "text", "label": "Very long value", "scope": "overview" }, + { "type": "text", "label": "", "scope": "overview" }, + { "type": "badge", "label": "Tier", "scope": "overview" }, + { "type": "badge", "label": "Alert", "scope": "overview" }, + { "type": "badge", "label": "Region", "scope": "overview" } ] } diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6f5f6065..5474f66c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ name = "openusage" version = "0.4.2" description = "OpenUsage is an open source AI subscription limit tracker" authors = ["Robin Ebers"] -edition = "2026" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/App.tsx b/src/App.tsx index 9adeee7e..d46e36fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -423,6 +423,45 @@ function App() { const handleProbeResult = useCallback( (output: PluginOutput) => { + // DEBUG: stress-test mock plugin only — remove after visual QA + if (output.providerId === "mock") { + const _15d = 15 * 24 * 60 * 60 * 1000 + const _30d = _15d * 2 + const _resets = new Date(Date.now() + _15d).toISOString() + const _pastReset = new Date(Date.now() - 60_000).toISOString() + output = { + ...output, + lines: [ + // Pace statuses + { type: "progress", label: "Ahead pace", used: 30, limit: 100, format: { kind: "percent" }, resetsAt: _resets, periodDurationMs: _30d }, + { type: "progress", label: "On Track pace", used: 45, limit: 100, format: { kind: "percent" }, resetsAt: _resets, periodDurationMs: _30d }, + { type: "progress", label: "Behind pace", used: 65, limit: 100, format: { kind: "percent" }, resetsAt: _resets, periodDurationMs: _30d }, + // Edge: 0% used + { type: "progress", label: "Empty bar", used: 0, limit: 500, format: { kind: "dollars" } }, + // Edge: 100% used (exactly at limit) + { type: "progress", label: "Exactly full", used: 1000, limit: 1000, format: { kind: "count", suffix: "tokens" } }, + // Edge: over limit + { type: "progress", label: "Over limit!", used: 1337, limit: 1000, format: { kind: "count", suffix: "requests" } }, + // Edge: huge numbers + { type: "progress", label: "Huge numbers", used: 8_429_301, limit: 10_000_000, format: { kind: "count", suffix: "tokens" } }, + // Edge: tiny sliver + { type: "progress", label: "Tiny sliver", used: 1, limit: 10000, format: { kind: "percent" } }, + // Edge: nearly full + { type: "progress", label: "Almost full", used: 9999, limit: 10000, format: { kind: "percent" } }, + // Edge: reset already passed + { type: "progress", label: "Expired reset", used: 42, limit: 100, format: { kind: "percent" }, resetsAt: _pastReset, periodDurationMs: _30d }, + // Text lines + { type: "text", label: "Status", value: "Active" }, + { type: "text", label: "Very long value", value: "This is an extremely long value string that should test text overflow and wrapping behavior in the card layout" }, + { type: "text", label: "", value: "Empty label" }, + // Badge lines + { type: "badge", label: "Tier", text: "Enterprise", color: "#8B5CF6" }, + { type: "badge", label: "Alert", text: "Rate limited", color: "#ef4444" }, + { type: "badge", label: "Region", text: "us-east-1" }, + ], + } + } + // END DEBUG const errorMessage = getErrorMessage(output) const isManual = manualRefreshIdsRef.current.has(output.providerId) if (isManual) { diff --git a/src/components/side-nav.test.tsx b/src/components/side-nav.test.tsx index 163a342d..093ed588 100644 --- a/src/components/side-nav.test.tsx +++ b/src/components/side-nav.test.tsx @@ -43,7 +43,7 @@ describe("SideNav", () => { expect(icon).toHaveStyle({ backgroundColor: "#ff0000" }) }) - it("falls back to currentColor for extremely light/dark brand colors", () => { + it("falls back to currentColor (light) or white (dark) for low-contrast brand colors", () => { const onViewChange = vi.fn() // Light mode + very light color => currentColor @@ -58,7 +58,7 @@ describe("SideNav", () => { const pStyle = screen.getByRole("img", { name: "P" }).getAttribute("style") ?? "" expect(pStyle).toMatch(/background-color:\s*currentcolor/i) - // Dark mode + very dark color => currentColor + // Dark mode + very dark color => white darkModeState.useDarkModeMock.mockReturnValueOnce(true) rerender( { /> ) const p2Style = screen.getByRole("img", { name: "P2" }).getAttribute("style") ?? "" - expect(p2Style).toMatch(/background-color:\s*currentcolor/i) + expect(p2Style).toContain("rgb(255, 255, 255)") }) }) diff --git a/src/components/side-nav.tsx b/src/components/side-nav.tsx index 148e87bf..b7758e93 100644 --- a/src/components/side-nav.tsx +++ b/src/components/side-nav.tsx @@ -45,7 +45,7 @@ function NavButton({ isActive, onClick, children, "aria-label": ariaLabel }: Nav "relative flex items-center justify-center w-full p-2.5 transition-colors", "hover:bg-accent", isActive - ? "text-foreground before:absolute before:left-0 before:top-1.5 before:bottom-1.5 before:w-0.5 before:bg-primary before:rounded-full" + ? "text-foreground before:absolute before:left-0 before:top-1.5 before:bottom-1.5 before:w-0.5 before:bg-primary dark:before:bg-page-accent before:rounded-full" : "text-muted-foreground" )} > @@ -57,7 +57,7 @@ function NavButton({ isActive, onClick, children, "aria-label": ariaLabel }: Nav function getIconColor(brandColor: string | undefined, isDark: boolean): string { if (!brandColor) return "currentColor" const luminance = getRelativeLuminance(brandColor) - if (isDark && luminance < 0.15) return "currentColor" + if (isDark && luminance < 0.15) return "#ffffff" if (!isDark && luminance > 0.85) return "currentColor" return brandColor } diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index 28f54838..fa9e2102 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -21,7 +21,7 @@ const Progress = React.forwardRef( aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={100} - className={cn("relative h-3 w-full overflow-hidden rounded-full bg-muted", className)} + className={cn("relative h-3 w-full overflow-hidden rounded-full bg-muted dark:bg-[#353537]", className)} {...props} >
Date: Sat, 7 Feb 2026 12:14:32 +0400 Subject: [PATCH 2/3] feat(App): implement scroll detection and enhance content rendering - Added scroll detection functionality to the App component, allowing for dynamic visibility of a gradient overlay when content is scrollable. - Introduced a new scroll reference and state to manage scroll behavior. - Updated the layout to improve content rendering within the scrollable area. - Enhanced CSS to hide the scrollbar globally for a cleaner UI experience. --- src/App.tsx | 32 +++++++++++++++++++++++++++----- src/components/side-nav.tsx | 2 +- src/index.css | 8 ++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d46e36fd..32ce2899 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,6 +61,8 @@ type PluginState = { function App() { const [activeView, setActiveView] = useState("home"); const containerRef = useRef(null); + const scrollRef = useRef(null); + const [canScrollDown, setCanScrollDown] = useState(false); const [pluginStates, setPluginStates] = useState>({}) const [pluginsMeta, setPluginsMeta] = useState([]) const [pluginSettings, setPluginSettings] = useState(null) @@ -798,6 +800,23 @@ function App() { [pluginSettings, setLoadingForPlugins, setErrorForPlugins, startBatch, scheduleTrayIconUpdate] ) + // Detect whether the scroll area has overflow below + useEffect(() => { + const el = scrollRef.current + if (!el) return + const check = () => { + setCanScrollDown(el.scrollHeight - el.scrollTop - el.clientHeight > 1) + } + check() + el.addEventListener("scroll", check, { passive: true }) + const ro = new ResizeObserver(check) + ro.observe(el) + return () => { + el.removeEventListener("scroll", check) + ro.disconnect() + } + }, [activeView]) + // Render content based on active view const renderContent = () => { if (activeView === "home") { @@ -846,18 +865,21 @@ function App() {
-
+
-
-
- {renderContent()} +
+
+
+ {renderContent()} +
+
+