From 9b46bd106d5b37701782efb4fa6de635f2bc54bc Mon Sep 17 00:00:00 2001 From: magnusgp Date: Thu, 6 Nov 2025 18:06:40 +0100 Subject: [PATCH] chore: UI cleanup and reference link fix --- backend/app/services/insights/service.py | 10 ++- .../tests/integration/test_insights_share.py | 15 ++++ frontend/src/components/SessionHistory.tsx | 84 +++++++++++-------- .../insights/InsightsReportDialog.tsx | 47 ++++++----- .../components/insights/InsightsToolbar.tsx | 26 ++++-- 5 files changed, 119 insertions(+), 63 deletions(-) diff --git a/backend/app/services/insights/service.py b/backend/app/services/insights/service.py index b9aecf3..30fe159 100644 --- a/backend/app/services/insights/service.py +++ b/backend/app/services/insights/service.py @@ -2,6 +2,7 @@ from datetime import datetime from typing import Any +from urllib.parse import urlsplit from backend.app.models.insights import InsightResponse, InsightShareResponse, InsightWindow from backend.app.services.insights.aggregator import InsightAggregator @@ -129,6 +130,9 @@ def _response_with_expiry(self, entry: CacheEntry) -> InsightResponse: def _build_share_url(self, token: str) -> str: if not self._share_base_url: raise RuntimeError("Share base URL is not configured") - - base = self._share_base_url.rstrip("/") - return f"{base}/insights/share/{token}" + parsed = urlsplit(self._share_base_url) + if parsed.scheme and parsed.netloc: + origin = f"{parsed.scheme}://{parsed.netloc}" + else: + origin = self._share_base_url.rstrip("/") + return f"{origin}/share/{token}" diff --git a/backend/tests/integration/test_insights_share.py b/backend/tests/integration/test_insights_share.py index 3afc31d..eb2fd30 100644 --- a/backend/tests/integration/test_insights_share.py +++ b/backend/tests/integration/test_insights_share.py @@ -58,3 +58,18 @@ async def test_share_token_round_trip(tmp_path) -> None: finally: app.dependency_overrides.clear() await store.close() + + +@pytest.mark.asyncio +async def test_share_url_strips_path_component(tmp_path) -> None: + database_url = f"sqlite+aiosqlite:///{tmp_path/'insights_share_path.db'}" + service = InsightService( + database_url=database_url, + cache_ttl_seconds=30, + share_token_salt="test-share-salt", + share_base_url="https://clipnotes.example.com/insights", + ) + + url = service._build_share_url("abc123") + + assert url == "https://clipnotes.example.com/share/abc123" diff --git a/frontend/src/components/SessionHistory.tsx b/frontend/src/components/SessionHistory.tsx index e48bb0b..ca3ffaf 100644 --- a/frontend/src/components/SessionHistory.tsx +++ b/frontend/src/components/SessionHistory.tsx @@ -118,34 +118,38 @@ export function SessionHistory({ : "border-border-glass/75 bg-surface-glass/70 text-text-secondary hover:border-accent-primary/45 hover:text-text-primary", )} > -
-
-

-

+
+
+

+ + {accessibleName} +

+

Registered {formatTimestamp(clip.created_at)}

+
+
+ + {clip.status} - {accessibleName} -

-

Registered {formatTimestamp(clip.created_at)}

+ {clip.last_analysis_at ? ( +

Last analysis {formatTimestamp(clip.last_analysis_at)}

+ ) : ( +

Awaiting analysis

+ )} + {typeof clip.latency_ms === "number" ? ( +

Latency {clip.latency_ms} ms

+ ) : null} +
-
- - {clip.status} - - {clip.last_analysis_at ? ( -

Last analysis {formatTimestamp(clip.last_analysis_at)}

- ) : ( -

Awaiting analysis

- )} - {typeof clip.latency_ms === "number" ? ( -

Latency {clip.latency_ms} ms

- ) : null} +
@@ -178,20 +182,34 @@ export function SessionHistory({ : "border-border-glass/75 bg-surface-glass/70 text-text-secondary hover:border-accent-primary/45 hover:text-text-primary", )} > -
-
-

-

+
+
+

+ + {accessibleName} +

+

Updated {lastUpdated}

+
+
+ + {session.chats.length ? `${session.chats.length} chat${session.chats.length > 1 ? "s" : ""}` : "No chats yet"} - {accessibleName} -

-

Updated {lastUpdated}

+ {session.isChatting ? ( +

Preparing response…

+ ) : session.chatError ? ( +

Follow-up failed

+ ) : ( +

Ready for follow-ups

+ )} +
-
+
-

+

The report opens in a new browser tab. Use your browser print dialog to save it as PDF or send it to stakeholders.

diff --git a/frontend/src/components/insights/InsightsToolbar.tsx b/frontend/src/components/insights/InsightsToolbar.tsx index 0eda354..13df7e2 100644 --- a/frontend/src/components/insights/InsightsToolbar.tsx +++ b/frontend/src/components/insights/InsightsToolbar.tsx @@ -12,10 +12,18 @@ interface InsightsToolbarProps { generatedAt?: string | null; cacheExpiresAt?: string | null; } - -const WINDOW_OPTIONS: Array<{ value: InsightWindow; label: string; description: string }> = [ - { value: "24h", label: "24 hours", description: "Hourly trend" }, - { value: "7d", label: "7 days", description: "Daily trend" }, +const WINDOW_OPTIONS: Array<{ value: InsightWindow; label: string; description: string; disabled?: boolean }> = [ + { + value: "24h", + label: "24 hours", + description: "Currently showing the last 24 hours while we polish the weekly view.", + }, + { + value: "7d", + label: "7 days", + description: "Daily trend (coming soon)", + disabled: true, + }, ]; function formatTimestamp(value: string | null | undefined): string | null { @@ -58,14 +66,20 @@ export function InsightsToolbar({ "rounded-full px-3 py-1.5 transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40", option.value === window ? "bg-accent-primary/20 text-accent-primary" - : "text-text-secondary hover:text-text-primary", + : option.disabled + ? "text-text-secondary/50" + : "text-text-secondary hover:text-text-primary", )} onClick={() => { + if (option.disabled) { + return; + } if (option.value !== window) { onWindowChange(option.value); } }} - disabled={isLoading || isRegenerating} + disabled={isLoading || isRegenerating || option.disabled} + title={option.disabled ? "7-day insights are coming soon" : undefined} > {option.label}