feat: Prevent duplicate secure storage unavailability warnings, include segments in meeting listings, and simplify ORM converter docstrings.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user