diff --git a/client/src/components/features/analytics/entities-tab.tsx b/client/src/components/features/analytics/entities-tab.tsx
index 827229c..03ee730 100644
--- a/client/src/components/features/analytics/entities-tab.tsx
+++ b/client/src/components/features/analytics/entities-tab.tsx
@@ -132,7 +132,12 @@ export function EntitiesTab({ entityAnalytics }: EntitiesTabProps) {
labelLine={false}
>
{categoryData.map((entry) => (
- |
+ |
))}
{
}
}
+// Track if we've already logged the unavailable warning to avoid spam
+let hasLoggedUnavailable = false;
+
export function isSecureStorageAvailable(): boolean {
try {
const hasWindow = typeof window !== 'undefined';
@@ -334,11 +337,12 @@ export function isSecureStorageAvailable(): boolean {
const available = hasWindow && hasCrypto && hasSubtle && hasGetRandomValues;
- if (!available && hasWindow) {
+ if (!available && hasWindow && !hasLoggedUnavailable) {
+ hasLoggedUnavailable = true;
addClientLog({
level: 'warning',
source: 'app',
- message: 'Secure storage availability check failed',
+ message: 'Secure storage unavailable - crypto.subtle requires secure context (HTTPS or localhost)',
metadata: {
hasWindow: String(hasWindow),
hasCrypto: String(hasCrypto),
@@ -351,12 +355,15 @@ export function isSecureStorageAvailable(): boolean {
return available;
} catch (error) {
- addClientLog({
- level: 'warning',
- source: 'app',
- message: 'Secure storage availability check threw',
- details: error instanceof Error ? error.message : String(error),
- });
+ if (!hasLoggedUnavailable) {
+ hasLoggedUnavailable = true;
+ addClientLog({
+ level: 'warning',
+ source: 'app',
+ message: 'Secure storage availability check threw',
+ details: error instanceof Error ? error.message : String(error),
+ });
+ }
return false;
}
}
diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx
index 59f77a8..5b7d71a 100644
--- a/client/src/pages/Home.tsx
+++ b/client/src/pages/Home.tsx
@@ -27,7 +27,7 @@ export default function HomePage() {
queryKey: ['home', 'meetings'],
queryFn: () =>
getAPI()
- .listMeetings({ limit: HOME_RECENT_MEETINGS_LIMIT, sort_order: 'newest' })
+ .listMeetings({ limit: HOME_RECENT_MEETINGS_LIMIT, sort_order: 'newest', include_segments: true })
.then((r) => r.meetings),
staleTime: THIRTY_SECONDS_MS,
});
diff --git a/client/src/pages/Meetings.tsx b/client/src/pages/Meetings.tsx
index d6d719b..6a5a675 100644
--- a/client/src/pages/Meetings.tsx
+++ b/client/src/pages/Meetings.tsx
@@ -75,6 +75,7 @@ export default function MeetingsPage() {
states: stateFilter === 'all' ? undefined : [stateFilter as MeetingState],
project_id: projectScope === 'active' ? resolvedProjectId : undefined,
project_ids: projectScope === 'selected' ? selectedIdsRef.current : undefined,
+ include_segments: true,
});
if (append) {
setMeetings((prev) => [...prev, ...response.meetings]);
diff --git a/src/noteflow/infrastructure/converters/orm_converters.py b/src/noteflow/infrastructure/converters/orm_converters.py
index 1566678..1052c1b 100644
--- a/src/noteflow/infrastructure/converters/orm_converters.py
+++ b/src/noteflow/infrastructure/converters/orm_converters.py
@@ -146,17 +146,7 @@ class OrmConverter:
@staticmethod
def word_timing_to_domain(model: WordTimingModel) -> DomainWordTiming:
- """Convert ORM WordTiming model to domain entity.
-
- Args:
- model: SQLAlchemy WordTimingModel instance.
-
- Returns:
- Domain WordTiming entity.
-
- Raises:
- ValueError: If timing validation fails during entity construction.
- """
+ """Convert ORM WordTiming model to domain entity."""
return DomainWordTiming(
word=model.word,
start_time=model.start_time,
@@ -168,18 +158,7 @@ class OrmConverter:
def word_timing_to_orm_kwargs(
word: DomainWordTiming, word_index: int
) -> dict[str, str | float | int]:
- """Convert domain WordTiming to ORM model kwargs.
-
- Return a dict of kwargs rather than instantiating WordTimingModel directly
- to avoid circular imports and allow the repository to handle ORM construction.
-
- Args:
- word: Domain WordTiming entity.
- word_index: Position of word in the segment.
-
- Returns:
- Dict with word, word_index, start_time, end_time, probability for ORM construction.
- """
+ """Convert domain WordTiming to ORM model kwargs (avoids circular imports)."""
return {
"word": word.word,
"word_index": word_index,
@@ -190,16 +169,16 @@ class OrmConverter:
@staticmethod
def meeting_to_domain(model: MeetingModel) -> Meeting:
- """Convert ORM Meeting model to domain entity.
+ """Convert ORM Meeting model to domain entity (includes summary if loaded)."""
+ meeting_id = MeetingId(model.id)
- Args:
- model: SQLAlchemy MeetingModel instance.
+ # Convert summary if loaded (eager-loaded via selectin relationship)
+ summary: Summary | None = None
+ if model.summary is not None:
+ summary = OrmConverter.summary_to_domain(model.summary, meeting_id)
- Returns:
- Domain Meeting entity.
- """
return Meeting(
- id=MeetingId(model.id),
+ id=meeting_id,
title=model.title,
workspace_id=model.workspace_id,
created_by_id=model.created_by_id,
@@ -216,6 +195,7 @@ class OrmConverter:
asset_path=model.asset_path,
version=model.version,
processing_status=OrmConverter.processing_status_from_payload(model.processing_status),
+ summary=summary,
)
@staticmethod