feat: Prevent duplicate secure storage unavailability warnings, include segments in meeting listings, and simplify ORM converter docstrings.

This commit is contained in:
2026-01-24 11:46:35 +00:00
parent 44d477de07
commit 09d70af58f
5 changed files with 33 additions and 40 deletions

View File

@@ -132,7 +132,12 @@ export function EntitiesTab({ entityAnalytics }: EntitiesTabProps) {
labelLine={false}
>
{categoryData.map((entry) => (
<Cell key={`cell-${entry.category}`} fill={entry.fill} />
<Cell
key={`cell-${entry.category}`}
fill={entry.fill}
stroke="hsl(var(--background))"
strokeWidth={2}
/>
))}
</Pie>
<Tooltip

View File

@@ -325,6 +325,9 @@ export async function checkSecureStorageHealth(): Promise<SecureStorageStatus> {
}
}
// 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;
}
}

View File

@@ -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,
});

View File

@@ -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]);

View File

@@ -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