From 19e39bed5ac327d14bbc2eda2beaadc82d02c22b Mon Sep 17 00:00:00 2001 From: Travis Vasceannie Date: Thu, 22 Jan 2026 15:34:56 +0000 Subject: [PATCH] big commit --- .coverage | Bin 69632 -> 0 bytes .../policies/opencode/ban_stdlib_logger.rego | 6 +- .../opencode/block_assertion_roulette.rego | 6 +- .../policies/opencode/block_biome_ignore.rego | 6 +- .../opencode/block_biome_ignore_bash.rego | 2 +- .../block_broad_exception_handler.rego | 6 +- .../block_code_quality_test_bash.rego | 2 +- .../block_code_quality_test_edits.rego | 4 +- .../block_code_quality_test_serena.rego | 2 +- ...block_code_quality_test_serena_plugin.rego | 2 +- .../opencode/block_datetime_now_fallback.rego | 6 +- .../opencode/block_default_value_swallow.rego | 6 +- .../opencode/block_duplicate_fixtures.rego | 6 +- .../block_linter_config_frontend.rego | 4 +- .../block_linter_config_frontend_bash.rego | 2 +- .../opencode/block_linter_config_python.rego | 4 +- .../block_linter_config_python_bash.rego | 2 +- .../opencode/block_magic_numbers.rego | 6 +- .../opencode/block_makefile_bash.rego | 2 +- .../opencode/block_makefile_edit.rego | 4 +- .../policies/opencode/block_no_verify.rego | 2 +- .../opencode/block_silent_none_return.rego | 6 +- .../block_test_loops_conditionals.rego | 6 +- .../opencode/block_tests_quality.rego | 4 +- .../opencode/block_tests_quality_bash.rego | 2 +- .cupcake/policies/opencode/example.rego | 2 +- .../policies/opencode/prevent_any_type.rego | 6 +- .../opencode/prevent_type_suppression.rego | 6 +- .../opencode/warn_baselines_edit.rego | 4 +- .../opencode/warn_baselines_edit_bash.rego | 2 +- .../policies/opencode/warn_large_file.rego | 6 +- .run_quality_check.sh | 4 + Makefile | 13 +- check_violations.py | 61 + client/e2e-native-mac/test-helpers.ts | 8 +- client/e2e/tasks.spec.ts | 86 + client/package-lock.json | 56 + client/package.json | 3 + client/src-tauri/cargo_output.txt | 437 + client/src-tauri/src/audio/capture.rs | 135 +- client/src-tauri/src/commands/analytics.rs | 17 +- client/src-tauri/src/commands/meeting.rs | 7 +- client/src-tauri/src/grpc/client/analytics.rs | 56 +- client/src-tauri/src/grpc/client/meetings.rs | 3 + client/src-tauri/src/grpc/noteflow.rs | 73 + client/src-tauri/src/grpc/types/analytics.rs | 23 + client/src-tauri/src/lib.rs | 3 +- client/src-tauri/tests/grpc_integration.rs | 4 +- client/src/api/adapters/cached/apps.test.ts | 22 + client/src/api/adapters/cached/audio.test.ts | 34 + .../src/api/adapters/cached/projects.test.ts | 89 + .../src/api/adapters/cached/templates.test.ts | 39 + .../src/api/adapters/cached/triggers.test.ts | 18 + client/src/api/adapters/mock/index.ts | 53 + .../tauri/__tests__/core-mapping.test.ts | 1 + .../__tests__/transcription-mapping.test.ts | 4 +- client/src/api/adapters/tauri/constants.ts | 1 + .../api/adapters/tauri/sections/analytics.ts | 13 +- .../api/adapters/tauri/sections/meetings.ts | 1 + .../adapters/tauri/sections/sections.test.ts | 350 + .../tauri/sections/summarization.test.ts | 90 + client/src/api/adapters/tauri/utils.test.ts | 32 + client/src/api/interface.ts | 4 + client/src/api/types/features/analytics.ts | 28 + client/src/api/types/features/sync.ts | 2 - client/src/api/types/requests/meetings.ts | 2 + client/src/api/types/requests/preferences.ts | 1 + .../badges/annotation-type-badge.test.tsx | 17 + .../dialogs/confirmation-dialog.test.tsx | 55 + .../components/common/error-boundary.test.tsx | 45 + .../src/components/common/nav-link.test.tsx | 30 + .../src/components/common/stats-card.test.tsx | 40 + client/src/components/dev/dev-profiler.tsx | 2 +- .../analytics/analytics-utils.test.ts | 38 + .../features/analytics/analytics-utils.ts | 23 + .../features/analytics/entities-tab.tsx | 216 + .../features/analytics/log-timeline.test.tsx | 114 + .../features/analytics/logs-tab-list.test.tsx | 131 + .../features/analytics/meetings-tab.tsx | 324 + .../calendar-connection-panel.test.tsx | 86 + .../calendar/calendar-events-panel.test.tsx | 101 + .../calendar/upcoming-meetings.test.tsx | 299 + .../connectivity/connection-status.test.tsx | 56 + ...server-switch-confirmation-dialog.test.tsx | 43 + .../entities/animated-transcription.test.tsx | 79 + .../notes/timestamped-notes-editor.tsx | 3 +- .../projects/ProjectScopeFilter.test.tsx | 89 + .../recording/in-transcript-search.test.tsx | 20 + .../recording/jump-to-live-indicator.test.tsx | 17 + .../advanced-local-ai-settings/index.tsx | 10 +- .../model-auth-section.tsx | 13 - .../streaming-config-section.tsx | 7 - .../advanced-local-ai-settings/summaries.ts | 29 + .../transcription-engine-section.tsx | 8 - .../settings/export-ai-section.test.tsx | 119 +- .../integrations-section/helpers.test.ts | 40 + .../settings/integrations-section/index.tsx | 4 +- ...xt.tsx => integration-settings-context.ts} | 19 +- .../integration-settings-provider.tsx | 19 + .../use-calendar-integration.ts | 9 +- .../use-integration-crud.ts | 5 +- .../features/tasks/TasksKanbanView.tsx | 363 + client/src/components/ui/icon-circle.tsx | 4 +- .../components/ui/markdown-editor-utils.ts | 114 + .../components/ui/markdown-editor.test.tsx | 187 + client/src/components/ui/markdown-editor.tsx | 139 +- .../ui/sidebar/{index.tsx => index.ts} | 1 - client/src/components/ui/sidebar/menu.tsx | 2 +- client/src/components/ui/slider.test.tsx | 29 + client/src/contexts/project-context.tsx | 3 + client/src/contexts/storage.test.ts | 38 + .../hooks/audio/use-streaming-config.test.tsx | 78 + client/src/hooks/auth/use-auth-flow.test.tsx | 231 + .../use-secure-integration-secrets.test.tsx | 106 + client/src/hooks/data/use-async-data.test.tsx | 117 + client/src/hooks/data/use-async-data.ts | 4 +- .../hooks/data/use-project-members.test.tsx | 54 + client/src/hooks/data/use-project.test.tsx | 46 + client/src/hooks/processing/events.test.tsx | 98 + .../processing/use-entity-extraction.test.tsx | 124 + .../use-recording-app-policy.test.tsx | 224 + .../src/hooks/sync/use-calendar-sync.test.tsx | 188 + .../sync/use-integration-validation.test.tsx | 122 + .../hooks/sync/use-meeting-reminders.test.tsx | 159 + .../hooks/sync/use-preferences-sync.test.tsx | 156 + client/src/hooks/sync/use-webhooks.test.tsx | 126 + .../src/hooks/ui/use-animated-words.test.ts | 38 + client/src/hooks/ui/use-mobile.test.tsx | 49 + .../hooks/ui/use-recording-panels.test.tsx | 87 + .../ai-providers/strategies/custom.test.ts | 86 + .../ai-providers/strategies/google.test.ts | 79 + client/src/lib/constants/timing.ts | 2 +- client/src/lib/integrations/oauth.test.ts | 113 + client/src/lib/preferences/api.test.ts | 199 + client/src/lib/preferences/api.ts | 20 +- client/src/lib/preferences/constants.ts | 1 + .../src/lib/preferences/integrations.test.ts | 45 + .../lib/preferences/local-only-keys.test.ts | 14 + client/src/lib/preferences/storage.test.ts | 270 + client/src/lib/preferences/tags.test.ts | 69 + client/src/lib/preferences/tauri.test.ts | 256 + client/src/lib/storage/crypto.test.ts | 86 + client/src/lib/storage/utils.test.ts | 91 + client/src/lib/system/events.test.tsx | 4 +- client/src/lib/utils/event-emitter.test.ts | 72 + client/src/lib/utils/id.test.ts | 21 + client/src/lib/utils/polling.test.ts | 189 + client/src/lib/utils/polling.ts | 8 +- client/src/pages/Analytics.test.tsx | 215 + client/src/pages/Analytics.tsx | 406 +- client/src/pages/Home.test.tsx | 201 + client/src/pages/MeetingDetail.test.tsx | 284 + client/src/pages/Meetings.test.tsx | 253 + client/src/pages/Meetings.tsx | 121 +- client/src/pages/NotFound.test.tsx | 26 + client/src/pages/People.tsx | 1 + client/src/pages/Projects.test.tsx | 16 + client/src/pages/Tasks.test.tsx | 154 + client/src/pages/Tasks.tsx | 106 +- .../meeting-detail/entities-panel.test.tsx | 56 + .../src/pages/meeting-detail/header.test.tsx | 157 + .../meeting-detail/summary-panel.test.tsx | 66 + .../meeting-detail/use-meeting-detail.ts | 2 +- client/vitest_output.txt | 8685 ++++++++++++ client/wdio.mac.conf.ts | 33 +- coverage_output.txt | 29 + .../Bugfinder/strategy-b-implementation.md | 62 +- pytest_output.txt | 29 + run_test.sh | 4 + .../application/services/analytics/refresh.py | 0 .../application/services/analytics/service.py | 120 +- src/noteflow/domain/constants/fields.py | 5 + src/noteflow/domain/constants/processing.py | 13 + src/noteflow/domain/entities/__init__.py | 12 +- src/noteflow/domain/entities/analytics.py | 48 + src/noteflow/domain/value_objects.py | 29 +- .../grpc/mixins/_processing_status.py | 31 +- src/noteflow/grpc/mixins/analytics_mixin.py | 50 +- .../grpc/mixins/converters/__init__.py | 4 + .../grpc/mixins/converters/_external.py | 11 +- .../grpc/mixins/converters/_task_analytics.py | 28 +- src/noteflow/grpc/mixins/diarization/_jobs.py | 12 +- .../grpc/mixins/diarization/_status.py | 9 +- src/noteflow/grpc/mixins/entities.py | 3 +- src/noteflow/grpc/mixins/errors/_require.py | 5 +- .../grpc/mixins/meeting/meeting_mixin.py | 30 +- src/noteflow/grpc/mixins/webhooks.py | 5 +- src/noteflow/grpc/proto/noteflow.proto | 34 + src/noteflow/grpc/proto/noteflow_pb2.py | 852 +- src/noteflow/grpc/proto/noteflow_pb2.pyi | 11388 ++++------------ src/noteflow/grpc/proto/noteflow_pb2_grpc.py | 45 +- src/noteflow/grpc/service.py | 11 +- src/noteflow/grpc/servicer/info_mixin.py | 83 + src/noteflow/grpc/servicer/mixins.py | 92 +- .../converters/integration_converters.py | 4 +- .../converters/orm_converters.py | 33 +- .../usage/_usage_event_builders.py | 5 +- ...z0a1b2_add_analytics_materialized_views.py | 231 + .../repositories/_analytics_converters.py | 112 + .../repositories/_analytics_entity_queries.py | 158 + .../repositories/_analytics_queries.py | 118 +- .../_materialized_view_queries.py | 321 + .../repositories/analytics_repo.py | 232 +- tests/application/test_analytics_service.py | 68 +- tests/cli/__init__.py | 1 + tests/cli/conftest.py | 201 + tests/cli/test_main.py | 110 + tests/cli/test_models.py | 200 + tests/cli/test_retention.py | 180 + tests/infrastructure/test_platform.py | 155 + tests/integration/conftest.py | 117 +- .../integration/test_analytics_repository.py | 68 + tests/integration/test_task_repository.py | 245 + 213 files changed, 24507 insertions(+), 10397 deletions(-) delete mode 100644 .coverage create mode 100644 .run_quality_check.sh create mode 100644 check_violations.py create mode 100644 client/e2e/tasks.spec.ts create mode 100644 client/src-tauri/cargo_output.txt create mode 100644 client/src/api/adapters/cached/apps.test.ts create mode 100644 client/src/api/adapters/cached/audio.test.ts create mode 100644 client/src/api/adapters/cached/projects.test.ts create mode 100644 client/src/api/adapters/cached/templates.test.ts create mode 100644 client/src/api/adapters/cached/triggers.test.ts create mode 100644 client/src/api/adapters/tauri/sections/sections.test.ts create mode 100644 client/src/api/adapters/tauri/sections/summarization.test.ts create mode 100644 client/src/api/adapters/tauri/utils.test.ts create mode 100644 client/src/components/common/badges/annotation-type-badge.test.tsx create mode 100644 client/src/components/common/dialogs/confirmation-dialog.test.tsx create mode 100644 client/src/components/common/error-boundary.test.tsx create mode 100644 client/src/components/common/nav-link.test.tsx create mode 100644 client/src/components/common/stats-card.test.tsx create mode 100644 client/src/components/features/analytics/analytics-utils.test.ts create mode 100644 client/src/components/features/analytics/entities-tab.tsx create mode 100644 client/src/components/features/analytics/log-timeline.test.tsx create mode 100644 client/src/components/features/analytics/logs-tab-list.test.tsx create mode 100644 client/src/components/features/analytics/meetings-tab.tsx create mode 100644 client/src/components/features/calendar/calendar-connection-panel.test.tsx create mode 100644 client/src/components/features/calendar/calendar-events-panel.test.tsx create mode 100644 client/src/components/features/calendar/upcoming-meetings.test.tsx create mode 100644 client/src/components/features/connectivity/connection-status.test.tsx create mode 100644 client/src/components/features/connectivity/server-switch-confirmation-dialog.test.tsx create mode 100644 client/src/components/features/entities/animated-transcription.test.tsx create mode 100644 client/src/components/features/projects/ProjectScopeFilter.test.tsx create mode 100644 client/src/components/features/recording/in-transcript-search.test.tsx create mode 100644 client/src/components/features/recording/jump-to-live-indicator.test.tsx create mode 100644 client/src/components/features/settings/advanced-local-ai-settings/summaries.ts create mode 100644 client/src/components/features/settings/integrations-section/helpers.test.ts rename client/src/components/features/settings/integrations-section/{integration-settings-context.tsx => integration-settings-context.ts} (72%) create mode 100644 client/src/components/features/settings/integrations-section/integration-settings-provider.tsx create mode 100644 client/src/components/features/tasks/TasksKanbanView.tsx create mode 100644 client/src/components/ui/markdown-editor-utils.ts create mode 100644 client/src/components/ui/markdown-editor.test.tsx rename client/src/components/ui/sidebar/{index.tsx => index.ts} (97%) create mode 100644 client/src/components/ui/slider.test.tsx create mode 100644 client/src/contexts/storage.test.ts create mode 100644 client/src/hooks/audio/use-streaming-config.test.tsx create mode 100644 client/src/hooks/auth/use-auth-flow.test.tsx create mode 100644 client/src/hooks/auth/use-secure-integration-secrets.test.tsx create mode 100644 client/src/hooks/data/use-async-data.test.tsx create mode 100644 client/src/hooks/data/use-project-members.test.tsx create mode 100644 client/src/hooks/data/use-project.test.tsx create mode 100644 client/src/hooks/processing/events.test.tsx create mode 100644 client/src/hooks/processing/use-entity-extraction.test.tsx create mode 100644 client/src/hooks/recording/use-recording-app-policy.test.tsx create mode 100644 client/src/hooks/sync/use-calendar-sync.test.tsx create mode 100644 client/src/hooks/sync/use-integration-validation.test.tsx create mode 100644 client/src/hooks/sync/use-meeting-reminders.test.tsx create mode 100644 client/src/hooks/sync/use-preferences-sync.test.tsx create mode 100644 client/src/hooks/sync/use-webhooks.test.tsx create mode 100644 client/src/hooks/ui/use-animated-words.test.ts create mode 100644 client/src/hooks/ui/use-mobile.test.tsx create mode 100644 client/src/hooks/ui/use-recording-panels.test.tsx create mode 100644 client/src/lib/ai-providers/strategies/custom.test.ts create mode 100644 client/src/lib/ai-providers/strategies/google.test.ts create mode 100644 client/src/lib/integrations/oauth.test.ts create mode 100644 client/src/lib/preferences/api.test.ts create mode 100644 client/src/lib/preferences/integrations.test.ts create mode 100644 client/src/lib/preferences/local-only-keys.test.ts create mode 100644 client/src/lib/preferences/storage.test.ts create mode 100644 client/src/lib/preferences/tags.test.ts create mode 100644 client/src/lib/preferences/tauri.test.ts create mode 100644 client/src/lib/storage/utils.test.ts create mode 100644 client/src/lib/utils/event-emitter.test.ts create mode 100644 client/src/lib/utils/id.test.ts create mode 100644 client/src/lib/utils/polling.test.ts create mode 100644 client/src/pages/Analytics.test.tsx create mode 100644 client/src/pages/Home.test.tsx create mode 100644 client/src/pages/MeetingDetail.test.tsx create mode 100644 client/src/pages/Meetings.test.tsx create mode 100644 client/src/pages/NotFound.test.tsx create mode 100644 client/src/pages/Projects.test.tsx create mode 100644 client/src/pages/Tasks.test.tsx create mode 100644 client/src/pages/meeting-detail/entities-panel.test.tsx create mode 100644 client/src/pages/meeting-detail/header.test.tsx create mode 100644 client/src/pages/meeting-detail/summary-panel.test.tsx create mode 100644 client/vitest_output.txt create mode 100644 coverage_output.txt create mode 100644 pytest_output.txt create mode 100644 run_test.sh create mode 100644 src/noteflow/application/services/analytics/refresh.py create mode 100644 src/noteflow/domain/constants/processing.py create mode 100644 src/noteflow/grpc/servicer/info_mixin.py create mode 100644 src/noteflow/infrastructure/persistence/migrations/versions/w7x8y9z0a1b2_add_analytics_materialized_views.py create mode 100644 src/noteflow/infrastructure/persistence/repositories/_analytics_converters.py create mode 100644 src/noteflow/infrastructure/persistence/repositories/_analytics_entity_queries.py create mode 100644 src/noteflow/infrastructure/persistence/repositories/_materialized_view_queries.py create mode 100644 tests/cli/__init__.py create mode 100644 tests/cli/conftest.py create mode 100644 tests/cli/test_main.py create mode 100644 tests/cli/test_models.py create mode 100644 tests/cli/test_retention.py create mode 100644 tests/infrastructure/test_platform.py create mode 100644 tests/integration/test_analytics_repository.py create mode 100644 tests/integration/test_task_repository.py diff --git a/.coverage b/.coverage deleted file mode 100644 index 0665735a19e11fce332fdea34d92e16ca0dc0664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69632 zcmeI53v?7!n#ZfUtGoJ9UDZiQI!{s|guK%UfrOw!(ux8yfP#RaG?h*zG<0``uI}(~ zlr$*p&a6AT&RN|#D6r?utUJD7oJH306?8nKj;~o2-5t-!tg9fdiV7;AWWT!Iom4{b zbkaFz4$N04$yar&?!Uk9cVAWacDk2bx4<8f#ZDz0lpY+pQ6)Js?NIl1~oD|2nR`eooucIpWeb&w@E(JWZ^yiLlkyifv+8 z*(i3)VX@O6kVSu}!|#(K{!o_~St}>6j;eBpHqb#D40YOtbtJ#G=k<3$u87|YD%Dhex@+sjC&HMJS6!NW=)f!P6h|s5ipPM4_Xv0d-9Z|%9 zKSaU|ss4yx35oJ1*%yt-9U}%X5w=w8f*vT>!QJKt#BYm{DmXRg>qgfZ$wE1n{wHVP?m%74k^wga;-$; zxuPNehNwJH_%-5S&KeR%NU0IEAfN`6+0Cw&ESiE^DQ{9Y1mJ$lD@7v;Irc)~dmG>n zcYB6~brl!WJ2#MuqT%tj`y=YWj&(?-8A!fB)zQvhxZY^3WN0PPT2UY%DxIXsXt~9B z;XrNHw;6X5h7{Hsny4f$1W*5>wM}ytd=T3MO8d~dMrw>ILj!@nAbDA5H00BoimJun zQ+njE)Fsy?&c?fizjQ@{s?%UKyXF*MsOk(<0dK>=t}T{lW?cmZ^foK0Mv2l&7)fKwRFL zN9YHk)7O5DLtNHU23@{Z-dx)w1)w+Smi%F8vd{;|M*vZ~utS22rFJD6A@x@~B-K+J zdj|cD3k)77*~3+pMZ1-Y71OM%x|)tf6OA$6&v;cB)Pqve z?ZlyBNF^C|a)hfc*dC{lyee_8W2N> zNB{{S0VIF~kN^@u0!RP}AOR$R1O^Cj#v(d74PdYt3#^G*025rHz5gG!4E!H}0bIC) z1dsp{Kmter2_OL^fCP{L5Y&1YLwD6D?lMpwTPzle#I(C)QLP5-B!)E}#Ec~6YNti2?3&ny<;020*kAIbaoZrX)l>ZUGiC@p($S>lr;VihgKmter2_OL^fCP}hB}gEzg`us}W_YUEOhWgh?XSEp9QkfW zR5*Uy`d1#N#QrO4`%k{{&BHraea80PoZSf5)igY?t&J6r9)0_MW9wJ0{x5`_sgPXP zcs$2Iwf6Q;roQ?5i~i@nexJ28Kze4qr=2dThrO$+J+Ix@MmfbkQKaZrilI1Kq+$)6 zwr|dHYa%tCefQEuD|&Z7en4 z<>}VlH*`F*di{+jUON5s{Cm`2zHO=1Ub51Y>$KOvUark!G8a+4jA}TTZ1q&vuXxb- z&FRnHd(BW-1xYhm&vLO<`|@>a&HiQViJJDUxBcnkuG5^Y63*sWs_0J{3tIvEPV=)S zt{isq-4C=Isd{P_H$^+Dd*E3!HOt7bW!gox|4rvlhMlI0;}8A=eXMVvK)w9!E1y{F zJO6y^V~1B}mBP7-a?g+3dT;K#srQwxw&24zOq>LXv!-}VYn-nOl!aMA8P+&ksdjFA zZ>#96H)oc>`6*?dUDWUQQrUaw(T`EIU4*pU$(~(j-#uk1Du#p0OFakgYV+2Yil!yA zsTbZ@^wGKRo;vX#dk^GTCc^2=NzZog&nbevx)M)y4fW@xEiaf_Z++%?Va{Gdc_E}P z7Ci^wX}RI?Rs3CFF6*D!d)D;Br#cs&x^LsX`ydM0a~Jay7heDuPApy*E1`~2eYtdD zJ{-@S=y8=%^(TAXy{{hqdS>tG&1dfEf9S0{>5M!$JGscC?mK$!zz2`~SK%Y9H5Za5 z6nc(`PO}Skd6U7C12(guY7OnohP_FS<1Zh{JEBleQvC+|1W3uv_dHMKmWs|S?R1`} ztLrGWPwhSUm5I%S6lbo@mY)GTO*!jg&lRjcdcv{uAm`&4Y6nmK{?QtX6V7F3dnhJL zfW3;$}HrV00DzjCyEmg%9u&pl7 zzW=4Z-3?V?)e}#Njk90eV>l5wwfo3#)*Py1;eyo@JZ|R;uhJ)uQcoW!ylG9_p=YQA zhXtyB>kps2{L>9Hsh68SruIH%_~#5R(+t;7$?`1k=-y_CeENUy?zYdGWrDQZ>>k(N zKJnj-$4o1VsoC_O?0sLFHo5!W?x(45_qjAivwVNvwsLAU+q>}!YX7^YzOS?2G_poG=QM6vY&O6SZ!nl>#%kql+WLPB zuTsKU;iT}1@Sz~_$N6{p_xZQ@SB3Y4Cj~_~AUq=6FYFb5CcGp3LAXo!vG9^`yKt+} zBdirx3QL3q!W?0i&@9vomBJ*UK$svn1T+6TzK?$tW)AxKZ}`vo2l@N>yZN8P#MG7% zQ!$yC@={`^Od_VNgqX=9F{Q=COqxhcNf9w(Au+`T#7xX5rYMh?!dzlZE@BFDh;d{S zlRtr&yewjJ$&3J?UFu zh8Uxf7=wX<2>`PGPdP8)+J>Pa0VIF~kN^@u0!RP}AOR$R1dsp{KmwGu{*TZ9SP_r_ z59k)Dq;b zv^4O0*bmvg>~hv_e#Ugz^!KJKnN!R!n3aYX4I2!3{B@4Uc-FDjk;mAKPZ|r1>*?Q8 zkI|j99eE=GB!C2v01_A%0@pIJymY<}SMCbRp@h4y;5e;qrHB2mHtRhg>{)k`Jbn?>C(FK*ego#zA>W|dj z9q{|4h+hf0RXN<__sME9L~kMid~?lXV%e!`CP1mtU{DJCx5UMhv~m_Ov4XUebfIS7 z$i$|nDmG%ML!8tFJYgvlt4>QfEc=umIlNgXnsqf36H*mTP)t@3cihv>P#BqM$;3Mt zkjY3#CMe4h_|k5WagL#C3G;ETdZ1py^t3=LpFNmncQE18&el$d*@KvecB?dxP8 zY1ED@j9W)3q4R)Cy9p-v>M9RBs?glH3{jVW$SC@Jp90@BuPd9GqbOOQ66*AK>2tS? zrqK-hx_g}yrt6tld8+km#Ewher1hw}`oWB*V;xaa=bv#jO-L`M(=bdoZm_!7DnS_r z6sgA@mb(?z4eed-3@97jYS=e;pv$Jc|G$tGDB;&amr%^V$?xEs93MM==4j(S;&yTK zIm-U1-Dl6TJ#UNJCR<;#Zm||x4q3V^cJ>KYhK#r&0VIF~kN^_6_zB#=#A;K$Q5bf= zG4Q0?3AaF9VI>;s@WNOPciJ=8^Y%k$AhMuo{0lY#cw)!6~2m#Z0VptQ=%tGz|6Ct)CsM zPNU|kNx}8b_{`MOC#8X z9Ly7~?@%^|0G)L-(+R_BVM1+fBp3iCHjP@6Q1U|08I&T4nGQ?(f$B=Zyqt+OjIk8p zxfB{M%o3|^aL_w=2w}U&M^{qALv`z}V`63L`>P{Ro6aP#Hu$(VLQ5unS4A34gp6bE zF!Wd6_Go7((JN1*sN;J2?qy2afd=Frnatytjb@H)So4TG7N3BhoX*+;VegZ=BT>@Z zm5yfSVML#Fm12_i{{Kem042OA+#$^1Kj3%si+Qu-fTPQi%l($y!FlXw?ECG0`y|^R zZ98l=*7vOcWNovYw*1Nxu++03uzT2Ltj+wiIb<#|y=A)7x}t^ z7YyBoT>3d$rfu*t#aTaAQeDX85$ z^hh0W_cdQkBT!9GxBfrBI6W$LhJum=YdG7ZDhDea{Usj&egeT=;(l4?*g656ob;Q z-F(mb|J)qV%uZ4Bd)NQ3%?71ZTknWUx=>e40I}&Qx;0{`LvFrwt7%KJKsnVLx)GH} zSpT1s38F%Z?j~YSRuJ9#|D23;WCqs%=Qz`l8C?ILD}c->tHwZ3L)QPV=0Rzc4QW70 zS1UXY&`EXWF@bsb`v2vek=i=ClG1r_y&XJAs|lXJ{{I6TXcnjF(y&L25oT=`SV3-# z_iE?Q+AOkw+8A#oN1SV!%YtC4wQA&O6VpHo%xMRzE7>_FP)oH55A#bWa~%URiqKKNA|27J{$Nw1s^dM#s%eaO7%2=AX;F%7g+ Pop~L`b*T36_s{)5>WWw7 diff --git a/.cupcake/policies/opencode/ban_stdlib_logger.rego b/.cupcake/policies/opencode/ban_stdlib_logger.rego index d6f372f..dac7599 100644 --- a/.cupcake/policies/opencode/ban_stdlib_logger.rego +++ b/.cupcake/policies/opencode/ban_stdlib_logger.rego @@ -120,7 +120,7 @@ deny contains decision if { decision := { "rule_id": "PY-LOG-001", - "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.", + "reason": "Stdlib logging is blocked. Use `from noteflow.infrastructure.logging import get_logger` and `logger = get_logger(__name__)`.", "severity": "HIGH" } } @@ -139,7 +139,7 @@ deny contains decision if { decision := { "rule_id": "PY-LOG-001", - "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.", + "reason": "Stdlib logging is blocked. Use `from noteflow.infrastructure.logging import get_logger` and `logger = get_logger(__name__)`.", "severity": "HIGH" } } @@ -157,7 +157,7 @@ deny contains decision if { decision := { "rule_id": "PY-LOG-001", - "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.", + "reason": "Stdlib logging is blocked. Use `from noteflow.infrastructure.logging import get_logger` and `logger = get_logger(__name__)`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_assertion_roulette.rego b/.cupcake/policies/opencode/block_assertion_roulette.rego index 8c2671d..89c7dfd 100644 --- a/.cupcake/policies/opencode/block_assertion_roulette.rego +++ b/.cupcake/policies/opencode/block_assertion_roulette.rego @@ -132,7 +132,7 @@ deny contains decision if { decision := { "rule_id": "TEST-ASSERT-001", - "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.", + "reason": "Multiple bare asserts are blocked. Use one assert per test or add messages (e.g., `assert cond, 'why'`).", "severity": "HIGH" } } @@ -151,7 +151,7 @@ deny contains decision if { decision := { "rule_id": "TEST-ASSERT-001", - "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.", + "reason": "Multiple bare asserts are blocked. Use one assert per test or add messages (e.g., `assert cond, 'why'`).", "severity": "HIGH" } } @@ -169,7 +169,7 @@ deny contains decision if { decision := { "rule_id": "TEST-ASSERT-001", - "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.", + "reason": "Multiple bare asserts are blocked. Use one assert per test or add messages (e.g., `assert cond, 'why'`).", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_biome_ignore.rego b/.cupcake/policies/opencode/block_biome_ignore.rego index 35516ad..63ac521 100644 --- a/.cupcake/policies/opencode/block_biome_ignore.rego +++ b/.cupcake/policies/opencode/block_biome_ignore.rego @@ -132,7 +132,7 @@ deny contains decision if { decision := { "rule_id": "TS-LINT-002", - "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.", + "reason": "Biome/ESLint/TS ignore directives are blocked. Fix the lint issue or refactor; use `make lint-fix`.", "severity": "HIGH" } } @@ -151,7 +151,7 @@ deny contains decision if { decision := { "rule_id": "TS-LINT-002", - "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.", + "reason": "Biome/ESLint/TS ignore directives are blocked. Fix the lint issue or refactor; use `make lint-fix`.", "severity": "HIGH" } } @@ -169,7 +169,7 @@ deny contains decision if { decision := { "rule_id": "TS-LINT-002", - "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.", + "reason": "Biome/ESLint/TS ignore directives are blocked. Fix the lint issue or refactor; use `make lint-fix`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_biome_ignore_bash.rego b/.cupcake/policies/opencode/block_biome_ignore_bash.rego index e88d33d..826103b 100644 --- a/.cupcake/policies/opencode/block_biome_ignore_bash.rego +++ b/.cupcake/policies/opencode/block_biome_ignore_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "TS-LINT-001", - "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.", + "reason": "Biome/ESLint/TS ignore directives are blocked. Fix the lint issue or refactor; use `make lint-fix`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_broad_exception_handler.rego b/.cupcake/policies/opencode/block_broad_exception_handler.rego index c706d2f..a69db2d 100644 --- a/.cupcake/policies/opencode/block_broad_exception_handler.rego +++ b/.cupcake/policies/opencode/block_broad_exception_handler.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-001", - "reason": "Broad Exception handlers that only log are prohibited.", + "reason": "Broad `except Exception` with only logging is blocked. Catch specific exceptions or re-raise after logging.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-001", - "reason": "Broad Exception handlers that only log are prohibited.", + "reason": "Broad `except Exception` with only logging is blocked. Catch specific exceptions or re-raise after logging.", "severity": "HIGH" } } @@ -140,7 +140,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-001", - "reason": "Broad Exception handlers that only log are prohibited.", + "reason": "Broad `except Exception` with only logging is blocked. Catch specific exceptions or re-raise after logging.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_code_quality_test_bash.rego b/.cupcake/policies/opencode/block_code_quality_test_bash.rego index 692c70b..a19d8a0 100644 --- a/.cupcake/policies/opencode/block_code_quality_test_bash.rego +++ b/.cupcake/policies/opencode/block_code_quality_test_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "TS-QUALITY-001", - "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.", + "reason": "Direct edits to `client/src/test/code-quality.test.ts` are blocked. Fix code issues instead.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_code_quality_test_edits.rego b/.cupcake/policies/opencode/block_code_quality_test_edits.rego index 7f5ced7..5967cbc 100644 --- a/.cupcake/policies/opencode/block_code_quality_test_edits.rego +++ b/.cupcake/policies/opencode/block_code_quality_test_edits.rego @@ -106,7 +106,7 @@ deny contains decision if { decision := { "rule_id": "TS-QUALITY-002", - "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.", + "reason": "Direct edits to `client/src/test/code-quality.test.ts` are blocked. Fix code issues instead.", "severity": "HIGH" } } @@ -121,7 +121,7 @@ deny contains decision if { decision := { "rule_id": "TS-QUALITY-002", - "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.", + "reason": "Direct edits to `client/src/test/code-quality.test.ts` are blocked. Fix code issues instead.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_code_quality_test_serena.rego b/.cupcake/policies/opencode/block_code_quality_test_serena.rego index cff4ce8..0c80362 100644 --- a/.cupcake/policies/opencode/block_code_quality_test_serena.rego +++ b/.cupcake/policies/opencode/block_code_quality_test_serena.rego @@ -120,7 +120,7 @@ deny contains decision if { decision := { "rule_id": "TS-QUALITY-003", - "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.", + "reason": "Direct edits to `client/src/test/code-quality.test.ts` are blocked. Fix code issues instead.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_code_quality_test_serena_plugin.rego b/.cupcake/policies/opencode/block_code_quality_test_serena_plugin.rego index fb87a2b..9830472 100644 --- a/.cupcake/policies/opencode/block_code_quality_test_serena_plugin.rego +++ b/.cupcake/policies/opencode/block_code_quality_test_serena_plugin.rego @@ -120,7 +120,7 @@ deny contains decision if { decision := { "rule_id": "TS-QUALITY-004", - "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.", + "reason": "Direct edits to `client/src/test/code-quality.test.ts` are blocked. Fix code issues instead.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_datetime_now_fallback.rego b/.cupcake/policies/opencode/block_datetime_now_fallback.rego index f77719c..e729def 100644 --- a/.cupcake/policies/opencode/block_datetime_now_fallback.rego +++ b/.cupcake/policies/opencode/block_datetime_now_fallback.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "PY-DT-001", - "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.", + "reason": "Fallback to `datetime.now()` is blocked. Use `utc_now()` or require a caller-provided timestamp.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "PY-DT-001", - "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.", + "reason": "Fallback to `datetime.now()` is blocked. Use `utc_now()` or require a caller-provided timestamp.", "severity": "HIGH" } } @@ -140,7 +140,7 @@ deny contains decision if { decision := { "rule_id": "PY-DT-001", - "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.", + "reason": "Fallback to `datetime.now()` is blocked. Use `utc_now()` or require a caller-provided timestamp.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_default_value_swallow.rego b/.cupcake/policies/opencode/block_default_value_swallow.rego index b051cd3..3ddf177 100644 --- a/.cupcake/policies/opencode/block_default_value_swallow.rego +++ b/.cupcake/policies/opencode/block_default_value_swallow.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-002", - "reason": "Swallowing exceptions and returning defaults is prohibited.", + "reason": "Swallowing exceptions and returning defaults is blocked. Catch specific errors and re-raise or return a typed error/result.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-002", - "reason": "Swallowing exceptions and returning defaults is prohibited.", + "reason": "Swallowing exceptions and returning defaults is blocked. Catch specific errors and re-raise or return a typed error/result.", "severity": "HIGH" } } @@ -140,7 +140,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-002", - "reason": "Swallowing exceptions and returning defaults is prohibited.", + "reason": "Swallowing exceptions and returning defaults is blocked. Catch specific errors and re-raise or return a typed error/result.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_duplicate_fixtures.rego b/.cupcake/policies/opencode/block_duplicate_fixtures.rego index bf974af..a5af5a6 100644 --- a/.cupcake/policies/opencode/block_duplicate_fixtures.rego +++ b/.cupcake/policies/opencode/block_duplicate_fixtures.rego @@ -134,7 +134,7 @@ deny contains decision if { decision := { "rule_id": "TEST-FIX-001", - "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.", + "reason": "Duplicate global fixtures are blocked. Reuse fixtures in `tests/conftest.py`, or add new ones there.", "severity": "HIGH" } } @@ -154,7 +154,7 @@ deny contains decision if { decision := { "rule_id": "TEST-FIX-001", - "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.", + "reason": "Duplicate global fixtures are blocked. Reuse fixtures in `tests/conftest.py`, or add new ones there.", "severity": "HIGH" } } @@ -173,7 +173,7 @@ deny contains decision if { decision := { "rule_id": "TEST-FIX-001", - "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.", + "reason": "Duplicate global fixtures are blocked. Reuse fixtures in `tests/conftest.py`, or add new ones there.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_linter_config_frontend.rego b/.cupcake/policies/opencode/block_linter_config_frontend.rego index 3bff2fe..66c49c9 100644 --- a/.cupcake/policies/opencode/block_linter_config_frontend.rego +++ b/.cupcake/policies/opencode/block_linter_config_frontend.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "TS-CONFIG-002", - "reason": "Frontend linter/config file edits are prohibited.", + "reason": "Frontend lint/type config edits are blocked. Fix code or use `make lint-fix` / `make type-check`.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "TS-CONFIG-002", - "reason": "Frontend linter/config file edits are prohibited.", + "reason": "Frontend lint/type config edits are blocked. Fix code or use `make lint-fix` / `make type-check`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_linter_config_frontend_bash.rego b/.cupcake/policies/opencode/block_linter_config_frontend_bash.rego index a356217..eae0320 100644 --- a/.cupcake/policies/opencode/block_linter_config_frontend_bash.rego +++ b/.cupcake/policies/opencode/block_linter_config_frontend_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "TS-CONFIG-001", - "reason": "Frontend linter/config file edits are prohibited.", + "reason": "Frontend lint/type config edits are blocked. Fix code or use `make lint-fix` / `make type-check`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_linter_config_python.rego b/.cupcake/policies/opencode/block_linter_config_python.rego index f2f6479..8b6cbb9 100644 --- a/.cupcake/policies/opencode/block_linter_config_python.rego +++ b/.cupcake/policies/opencode/block_linter_config_python.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "PY-CONFIG-002", - "reason": "Python linter/config file edits are prohibited.", + "reason": "Python lint/type config edits are blocked. Fix code or use `make lint-fix-py` / `make type-check-py`.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "PY-CONFIG-002", - "reason": "Python linter/config file edits are prohibited.", + "reason": "Python lint/type config edits are blocked. Fix code or use `make lint-fix-py` / `make type-check-py`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_linter_config_python_bash.rego b/.cupcake/policies/opencode/block_linter_config_python_bash.rego index 65229c7..ba349e0 100644 --- a/.cupcake/policies/opencode/block_linter_config_python_bash.rego +++ b/.cupcake/policies/opencode/block_linter_config_python_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "PY-CONFIG-001", - "reason": "Python linter/config file edits are prohibited.", + "reason": "Python lint/type config edits are blocked. Fix code or use `make lint-fix-py` / `make type-check-py`.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_magic_numbers.rego b/.cupcake/policies/opencode/block_magic_numbers.rego index 54bac75..ee57787 100644 --- a/.cupcake/policies/opencode/block_magic_numbers.rego +++ b/.cupcake/policies/opencode/block_magic_numbers.rego @@ -133,7 +133,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-001", - "reason": "Magic numbers are prohibited. Use named constants.", + "reason": "Magic numbers are blocked. Define a named `typing.Final` constant (e.g., in domain/constants) and use it.", "severity": "HIGH" } } @@ -153,7 +153,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-001", - "reason": "Magic numbers are prohibited. Use named constants.", + "reason": "Magic numbers are blocked. Define a named `typing.Final` constant (e.g., in domain/constants) and use it.", "severity": "HIGH" } } @@ -172,7 +172,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-001", - "reason": "Magic numbers are prohibited. Use named constants.", + "reason": "Magic numbers are blocked. Define a named `typing.Final` constant (e.g., in domain/constants) and use it.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_makefile_bash.rego b/.cupcake/policies/opencode/block_makefile_bash.rego index 15e2b9a..0b1d8bf 100644 --- a/.cupcake/policies/opencode/block_makefile_bash.rego +++ b/.cupcake/policies/opencode/block_makefile_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "BUILD-001", - "reason": "Makefile edits are prohibited.", + "reason": "Makefile edits are blocked. Use existing `make` targets or request explicit permission.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_makefile_edit.rego b/.cupcake/policies/opencode/block_makefile_edit.rego index 9f7da15..c52d8bf 100644 --- a/.cupcake/policies/opencode/block_makefile_edit.rego +++ b/.cupcake/policies/opencode/block_makefile_edit.rego @@ -106,7 +106,7 @@ deny contains decision if { decision := { "rule_id": "BUILD-002", - "reason": "Makefile edits are prohibited.", + "reason": "Makefile edits are blocked. Use existing `make` targets or request explicit permission.", "severity": "HIGH" } } @@ -121,7 +121,7 @@ deny contains decision if { decision := { "rule_id": "BUILD-002", - "reason": "Makefile edits are prohibited.", + "reason": "Makefile edits are blocked. Use existing `make` targets or request explicit permission.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_no_verify.rego b/.cupcake/policies/opencode/block_no_verify.rego index d399d1f..994986c 100644 --- a/.cupcake/policies/opencode/block_no_verify.rego +++ b/.cupcake/policies/opencode/block_no_verify.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "GIT-001", - "reason": "Git commit --no-verify is prohibited.", + "reason": "`git commit --no-verify` is blocked. Run required checks (e.g., `make quality`) instead.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_silent_none_return.rego b/.cupcake/policies/opencode/block_silent_none_return.rego index bdb56b4..19891c5 100644 --- a/.cupcake/policies/opencode/block_silent_none_return.rego +++ b/.cupcake/policies/opencode/block_silent_none_return.rego @@ -107,7 +107,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-003", - "reason": "Silent exception handlers returning empty values are prohibited.", + "reason": "Silent exception handlers returning empty values are blocked. Propagate the error or return a typed Result; log with context if needed.", "severity": "HIGH" } } @@ -123,7 +123,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-003", - "reason": "Silent exception handlers returning empty values are prohibited.", + "reason": "Silent exception handlers returning empty values are blocked. Propagate the error or return a typed Result; log with context if needed.", "severity": "HIGH" } } @@ -140,7 +140,7 @@ deny contains decision if { decision := { "rule_id": "PY-EXC-003", - "reason": "Silent exception handlers returning empty values are prohibited.", + "reason": "Silent exception handlers returning empty values are blocked. Propagate the error or return a typed Result; log with context if needed.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_test_loops_conditionals.rego b/.cupcake/policies/opencode/block_test_loops_conditionals.rego index b706398..3fed848 100644 --- a/.cupcake/policies/opencode/block_test_loops_conditionals.rego +++ b/.cupcake/policies/opencode/block_test_loops_conditionals.rego @@ -132,7 +132,7 @@ deny contains decision if { decision := { "rule_id": "TEST-STRUCT-001", - "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.", + "reason": "Loops/conditionals inside tests are blocked. Use `@pytest.mark.parametrize` or split into separate tests.", "severity": "HIGH" } } @@ -151,7 +151,7 @@ deny contains decision if { decision := { "rule_id": "TEST-STRUCT-001", - "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.", + "reason": "Loops/conditionals inside tests are blocked. Use `@pytest.mark.parametrize` or split into separate tests.", "severity": "HIGH" } } @@ -169,7 +169,7 @@ deny contains decision if { decision := { "rule_id": "TEST-STRUCT-001", - "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.", + "reason": "Loops/conditionals inside tests are blocked. Use `@pytest.mark.parametrize` or split into separate tests.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_tests_quality.rego b/.cupcake/policies/opencode/block_tests_quality.rego index 45233fd..ecf1ef0 100644 --- a/.cupcake/policies/opencode/block_tests_quality.rego +++ b/.cupcake/policies/opencode/block_tests_quality.rego @@ -108,7 +108,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-002", - "reason": "Direct edits to tests/quality are prohibited (except baselines.json).", + "reason": "Direct edits to `tests/quality` are blocked. Fix the underlying code; only `tests/quality/baselines.json` may be edited with explicit approval.", "severity": "HIGH" } } @@ -124,7 +124,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-002", - "reason": "Direct edits to tests/quality are prohibited (except baselines.json).", + "reason": "Direct edits to `tests/quality` are blocked. Fix the underlying code; only `tests/quality/baselines.json` may be edited with explicit approval.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/block_tests_quality_bash.rego b/.cupcake/policies/opencode/block_tests_quality_bash.rego index e65e9b0..339e4ae 100644 --- a/.cupcake/policies/opencode/block_tests_quality_bash.rego +++ b/.cupcake/policies/opencode/block_tests_quality_bash.rego @@ -21,7 +21,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-001", - "reason": "Direct edits to tests/quality are prohibited (except baselines.json).", + "reason": "Direct edits to `tests/quality` are blocked. Fix the underlying code; only `tests/quality/baselines.json` may be edited with explicit approval.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/example.rego b/.cupcake/policies/opencode/example.rego index 0fb2487..74e818d 100644 --- a/.cupcake/policies/opencode/example.rego +++ b/.cupcake/policies/opencode/example.rego @@ -15,7 +15,7 @@ import rego.v1 deny contains decision if { input.tool_input.command == "CUPCAKE_EXAMPLE_RULE_THAT_NEVER_FIRES_12345" decision := { - "reason": "This will never happen", + "reason": "Example policy: replace this reason with actionable remediation guidance.", "severity": "LOW", "rule_id": "EXAMPLE-001" } diff --git a/.cupcake/policies/opencode/prevent_any_type.rego b/.cupcake/policies/opencode/prevent_any_type.rego index 89e4022..8b1bd95 100644 --- a/.cupcake/policies/opencode/prevent_any_type.rego +++ b/.cupcake/policies/opencode/prevent_any_type.rego @@ -137,7 +137,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-001", - "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.", + "reason": "`Any` is blocked in Python types. Prefer Protocol/TypeVar/TypedDict or concrete types; use `cast()` with a justification comment only as a last resort.", "severity": "HIGH" } } @@ -154,7 +154,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-001", - "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.", + "reason": "`Any` is blocked in Python types. Prefer Protocol/TypeVar/TypedDict or concrete types; use `cast()` with a justification comment only as a last resort.", "severity": "HIGH" } } @@ -175,7 +175,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-001", - "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.", + "reason": "`Any` is blocked in Python types. Prefer Protocol/TypeVar/TypedDict or concrete types; use `cast()` with a justification comment only as a last resort.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/prevent_type_suppression.rego b/.cupcake/policies/opencode/prevent_type_suppression.rego index e1ec86c..6c1b545 100644 --- a/.cupcake/policies/opencode/prevent_type_suppression.rego +++ b/.cupcake/policies/opencode/prevent_type_suppression.rego @@ -132,7 +132,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-002", - "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.", + "reason": "Type suppression directives are blocked. Fix the type issue using Protocol/TypeVar/TypedDict or `cast()` with a justification comment.", "severity": "HIGH" } } @@ -149,7 +149,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-002", - "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.", + "reason": "Type suppression directives are blocked. Fix the type issue using Protocol/TypeVar/TypedDict or `cast()` with a justification comment.", "severity": "HIGH" } } @@ -170,7 +170,7 @@ deny contains decision if { decision := { "rule_id": "PY-TYPE-002", - "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.", + "reason": "Type suppression directives are blocked. Fix the type issue using Protocol/TypeVar/TypedDict or `cast()` with a justification comment.", "severity": "HIGH" } } diff --git a/.cupcake/policies/opencode/warn_baselines_edit.rego b/.cupcake/policies/opencode/warn_baselines_edit.rego index 4d3410f..2def69a 100644 --- a/.cupcake/policies/opencode/warn_baselines_edit.rego +++ b/.cupcake/policies/opencode/warn_baselines_edit.rego @@ -106,7 +106,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-004", - "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.", + "reason": "Warning: avoid editing `tests/quality/baselines.json`. Prefer fixing code; if approved, keep baseline changes minimal and documented.", "severity": "LOW" } } @@ -121,7 +121,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-004", - "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.", + "reason": "Warning: avoid editing `tests/quality/baselines.json`. Prefer fixing code; if approved, keep baseline changes minimal and documented.", "severity": "LOW" } } diff --git a/.cupcake/policies/opencode/warn_baselines_edit_bash.rego b/.cupcake/policies/opencode/warn_baselines_edit_bash.rego index 0b67e3d..42fa8db 100644 --- a/.cupcake/policies/opencode/warn_baselines_edit_bash.rego +++ b/.cupcake/policies/opencode/warn_baselines_edit_bash.rego @@ -20,7 +20,7 @@ deny contains decision if { decision := { "rule_id": "TEST-QUALITY-003", - "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.", + "reason": "Warning: avoid editing `tests/quality/baselines.json`. Prefer fixing code; if approved, keep baseline changes minimal and documented.", "severity": "LOW" } } diff --git a/.cupcake/policies/opencode/warn_large_file.rego b/.cupcake/policies/opencode/warn_large_file.rego index db96c11..dc6568b 100644 --- a/.cupcake/policies/opencode/warn_large_file.rego +++ b/.cupcake/policies/opencode/warn_large_file.rego @@ -132,7 +132,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-002", - "reason": "Warning: file content exceeds 500 lines. Consider refactoring.", + "reason": "Warning: file exceeds 500 lines. Consider extracting helpers or splitting modules/classes.", "severity": "LOW" } } @@ -151,7 +151,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-002", - "reason": "Warning: file content exceeds 500 lines. Consider refactoring.", + "reason": "Warning: file exceeds 500 lines. Consider extracting helpers or splitting modules/classes.", "severity": "LOW" } } @@ -169,7 +169,7 @@ deny contains decision if { decision := { "rule_id": "STYLE-002", - "reason": "Warning: file content exceeds 500 lines. Consider refactoring.", + "reason": "Warning: file exceeds 500 lines. Consider extracting helpers or splitting modules/classes.", "severity": "LOW" } } diff --git a/.run_quality_check.sh b/.run_quality_check.sh new file mode 100644 index 0000000..f2813ac --- /dev/null +++ b/.run_quality_check.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd /home/trav/repos/noteflow +source .venv/bin/activate +pytest tests/quality/test_code_smells.py -v --tb=short 2>&1 | tail -100 diff --git a/Makefile b/Makefile index 295988d..4cf71e4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # NoteFlow Quality Checks # Runs TypeScript, Rust, and Python quality checks -.PHONY: all quality quality-ts quality-rs quality-py lint type-check test-quality \ +.PHONY: all quality quality-ts quality-rs quality-py lint type-check test-quality coverage coverage-ts \ lint-rs clippy fmt fmt-rs fmt-check check help e2e e2e-ui e2e-grpc \ ensure-py ensure-ts ensure-rs ensure-hygiene install-hooks uninstall-hooks @@ -100,6 +100,15 @@ test-quality: ensure-ts @echo "=== TypeScript Quality Tests ===" cd client && npm run test:quality +## Run Vitest with coverage +coverage-ts: ensure-ts + @echo "=== TypeScript Coverage (Vitest) ===" + cd client && npm run test -- --coverage + +## Run all coverage checks +coverage: coverage-ts + @echo "✓ Coverage checks passed" + #------------------------------------------------------------------------------- # Rust Quality Checks #------------------------------------------------------------------------------- @@ -257,6 +266,8 @@ help: @echo " check Run Biome check (lint + format)" @echo " check-fix Auto-fix all Biome issues" @echo " test-quality Run Vitest quality tests" + @echo " coverage Run coverage checks" + @echo " coverage-ts Run Vitest test coverage" @echo "" @echo "Rust:" @echo " clippy Run Clippy linter" diff --git a/check_violations.py b/check_violations.py new file mode 100644 index 0000000..0dfbf07 --- /dev/null +++ b/check_violations.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Quick script to check long parameter list violations.""" + +from __future__ import annotations + +import importlib +import sys +from collections.abc import Mapping +from typing import Protocol, cast + +sys.path.insert(0, "/home/trav/repos/noteflow/tests") +sys.path.insert(0, "/home/trav/repos/noteflow/src") + + +class Violation(Protocol): + stable_id: str + relative_path: str + identifier: str + detail: str + + +class CollectLongParameterLists(Protocol): + def __call__(self, *, parse_errors: list[str]) -> list[Violation]: ... + + +def load_collectors() -> tuple[CollectLongParameterLists, Mapping[str, set[str]]]: + code_smells = importlib.import_module("quality._detectors.code_smells") + baseline_module = importlib.import_module("quality._baseline") + collect = cast(CollectLongParameterLists, getattr(code_smells, "collect_long_parameter_lists")) + baseline = cast(Mapping[str, set[str]], getattr(baseline_module, "load_baseline")()) + return collect, baseline + + +collect_long_parameter_lists, baseline = load_collectors() + +parse_errors: list[str] = [] +violations = collect_long_parameter_lists(parse_errors=parse_errors) + +allowed_ids = baseline.get("long_parameter_list", set()) + +current_ids = {v.stable_id for v in violations} +new_ids = current_ids - allowed_ids + +new_violations = [v for v in violations if v.stable_id in new_ids] + +print(f"Total violations found: {len(violations)}") +print(f"Baseline allows: {len(allowed_ids)}") +print(f"NEW violations (not in baseline): {len(new_violations)}") +print() + +if new_violations: + print("NEW VIOLATIONS:") + for v in sorted(new_violations, key=lambda x: x.stable_id): + print(f" {v.relative_path}:{v.identifier} ({v.detail})") +else: + print("No new violations - test would PASS") + +if parse_errors: + print(f"\nParse errors: {len(parse_errors)}") + for e in parse_errors[:5]: + print(f" {e}") diff --git a/client/e2e-native-mac/test-helpers.ts b/client/e2e-native-mac/test-helpers.ts index 453ec92..b954ed6 100644 --- a/client/e2e-native-mac/test-helpers.ts +++ b/client/e2e-native-mac/test-helpers.ts @@ -6,7 +6,7 @@ */ import { invoke } from '@tauri-apps/api/core'; -import { TauriCommands } from '../src/api/tauri-constants'; +import { TauriCommands } from '../src/api/adapters/tauri/constants'; /** * Test environment information returned by check_test_environment. @@ -52,7 +52,7 @@ export interface TestAudioResult { * Check if the test environment is properly configured for audio tests. */ export async function checkTestEnvironment(): Promise { - return invoke(TauriCommands.CHECK_TEST_ENVIRONMENT); + return invoke(TauriCommands.CHECK_TEST_ENVIRONMENT); } /** @@ -66,7 +66,7 @@ export async function injectTestAudio( meetingId: string, config: TestAudioConfig ): Promise { - return invoke(TauriCommands.INJECT_TEST_AUDIO, { + return invoke(TauriCommands.INJECT_TEST_AUDIO, { meeting_id: meetingId, config, }); @@ -86,7 +86,7 @@ export async function injectTestTone( durationSeconds: number, sampleRate?: number ): Promise { - return invoke(TauriCommands.INJECT_TEST_TONE, { + return invoke(TauriCommands.INJECT_TEST_TONE, { meeting_id: meetingId, frequency_hz: frequencyHz, duration_seconds: durationSeconds, diff --git a/client/e2e/tasks.spec.ts b/client/e2e/tasks.spec.ts new file mode 100644 index 0000000..0c6899e --- /dev/null +++ b/client/e2e/tasks.spec.ts @@ -0,0 +1,86 @@ +import { expect, test } from '@playwright/test'; +import { callAPI, navigateTo, waitForAPI, waitForLoadingComplete } from './fixtures'; + +const shouldRun = process.env.NOTEFLOW_E2E === '1'; + +const TEST_WORKSPACE_ID = '00000000-0000-0000-0000-000000000001'; + +test.describe('tasks api integration', () => { + test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.'); + + test.beforeEach(async ({ page }) => { + await navigateTo(page, '/'); + await waitForAPI(page); + }); + + test('listTasks returns array of tasks', async ({ page }) => { + const result = await callAPI<{ tasks: unknown[]; total: number }>(page, 'listTasks', { + workspace_id: TEST_WORKSPACE_ID, + }); + + expect(result).toHaveProperty('tasks'); + expect(result).toHaveProperty('total'); + expect(Array.isArray(result.tasks)).toBe(true); + expect(typeof result.total).toBe('number'); + }); + + test('listTasks supports status filtering', async ({ page }) => { + const result = await callAPI<{ tasks: { status: string }[]; total: number }>( + page, + 'listTasks', + { + workspace_id: TEST_WORKSPACE_ID, + statuses: ['open'], + } + ); + + for (const task of result.tasks) { + expect(task.status).toBe('open'); + } + }); + + test('listTasks supports pagination', async ({ page }) => { + const pageSize = 5; + const page1 = await callAPI<{ tasks: { id: string }[]; total: number }>(page, 'listTasks', { + workspace_id: TEST_WORKSPACE_ID, + limit: pageSize, + offset: 0, + }); + + expect(page1.tasks.length).toBeLessThanOrEqual(pageSize); + + if (page1.total > pageSize) { + const page2 = await callAPI<{ tasks: { id: string }[] }>(page, 'listTasks', { + workspace_id: TEST_WORKSPACE_ID, + limit: pageSize, + offset: pageSize, + }); + + const page1Ids = page1.tasks.map((t) => t.id); + const page2Ids = page2.tasks.map((t) => t.id); + const overlap = page1Ids.filter((id) => page2Ids.includes(id)); + expect(overlap.length).toBe(0); + } + }); +}); + +test.describe('tasks page ui', () => { + test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.'); + + test('tasks page loads successfully', async ({ page }) => { + await navigateTo(page, '/tasks'); + await waitForLoadingComplete(page); + + const mainContent = page.locator('main'); + await expect(mainContent).toBeVisible(); + }); + + test('tasks page displays task list or empty state', async ({ page }) => { + await navigateTo(page, '/tasks'); + await waitForLoadingComplete(page); + + const taskList = page.locator('[data-testid="task-list"], [data-testid="empty-state"]'); + const hasContent = (await taskList.count()) > 0 || (await page.locator('main').isVisible()); + expect(hasContent).toBe(true); + }); +}); diff --git a/client/package-lock.json b/client/package-lock.json index 22c0a06..b4a458b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,9 @@ "name": "noteflow-client", "version": "0.1.0", "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -533,6 +536,59 @@ "node": ">=18" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz", + "integrity": "sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.1.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", diff --git a/client/package.json b/client/package.json index ed84c38..93deba3 100644 --- a/client/package.json +++ b/client/package.json @@ -61,6 +61,9 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.83.0", "@tanstack/react-virtual": "^3.13.13", "@tauri-apps/api": "^2.9.1", diff --git a/client/src-tauri/cargo_output.txt b/client/src-tauri/cargo_output.txt new file mode 100644 index 0000000..e3d1ce7 --- /dev/null +++ b/client/src-tauri/cargo_output.txt @@ -0,0 +1,437 @@ + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s + Running unittests src/lib.rs (target/debug/deps/noteflow_lib-38d0670d84e04361) + +running 323 tests +test audio::capture::tests::calculate_rms_i16_empty_returns_zero ... ok +test audio::capture::tests::rms_to_db_negative_returns_floor ... ok +test audio::capture::tests::calculate_rms_u16_empty_returns_zero ... ok +test audio::capture::tests::rms_to_db_silence_returns_floor ... ok +test audio::capture::tests::rms_to_db_unit_signal_is_zero ... ok +test audio::capture::tests::calculate_rms_empty_returns_zero ... ok +test audio::capture::tests::normalize_for_asr_empty_returns_unity_gain ... ok +test audio::capture::tests::calculate_rms_i16_min_value_within_bounds ... ok +test audio::capture::tests::calculate_rms_silence_returns_zero ... ok +test audio::capture::tests::calculate_rms_u16_midpoint_near_zero ... ok +test audio::capture::tests::normalize_for_asr_respects_max_gain ... ok +test audio::capture::tests::calculate_rms_unit_signal ... ok +test audio::capture::tests::soft_clip_linear_region ... ok +test audio::capture::tests::soft_clip_negative_saturation ... ok +test audio::capture::tests::normalize_for_asr_silence_returns_unity_gain ... ok +test audio::capture::tests::normalize_for_asr_boosts_quiet_audio ... ok +test audio::capture::tests::soft_clip_saturation_region ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_positive_drift ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_new ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_negative_drift ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_reset ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_returns_ratio_above_threshold ... ok +test audio::drift_compensation::metrics::tests::test_record_overflow ... ok +test audio::drift_compensation::metrics::tests::test_update_values ... ok +test audio::drift_compensation::detector::tests::test_drift_detector_no_drift ... ok +test audio::drift_compensation::metrics::tests::test_drift_metrics_new ... ok +test audio::drift_compensation::metrics::tests::test_record_adjustment ... ok +test audio::drift_compensation::metrics::tests::test_record_error ... ok +test audio::drift_compensation::metrics::tests::test_snapshot ... ok +test audio::drift_compensation::resampler::tests::test_adaptive_resampler_reset ... ok +test audio::drift_compensation::resampler::tests::test_interleave_stereo ... ok +test audio::drift_compensation::resampler::tests::test_deinterleave_stereo ... ok +test audio::drift_compensation::resampler::tests::test_deinterleave_mono ... ok +test audio::drift_compensation::metrics::tests::test_reset ... ok +test audio::drift_compensation::metrics::tests::test_set_enabled ... ok +test audio::drift_compensation::resampler::tests::test_adaptive_resampler_new ... ok +test audio::drift_compensation::resampler::tests::test_empty_input ... ok +test audio::drift_compensation::resampler::tests::test_adaptive_resampler_slew_limiting ... ok +test audio::drift_compensation::resampler::tests::test_adaptive_resampler_passthrough ... ok +test audio::drift_compensation::resampler::tests::test_adaptive_resampler_set_ratio ... ok +test audio::loader::tests::test_parse_samples ... ok +test audio::loader::tests::test_samples_to_chunks ... ok +test audio::mixer::tests::test_invalid_channels_fallback ... ok +test audio::mixer::tests::test_drift_compensation_enabled_by_default ... ok +test audio::mixer::tests::test_invalid_sample_rate_fallback ... ok +test audio::playback::tests::playback_command_debug ... ok +test audio::mixer::tests::test_mixer_clipping ... ok +test audio::mixer::tests::test_mixer_with_drift_compensation_disabled ... ok +test audio::mixer::tests::test_mixer_clear_resets_drift_state ... ok +test audio::playback::tests::playback_started_debug ... ok +test audio::mixer::tests::test_mixer_with_gains ... ok +test audio::mixer::tests::test_drift_compensation_toggle ... ok +test audio::mixer::tests::test_drift_metrics_snapshot ... ok +test audio::mixer::tests::test_mixer_basic ... ok +test audio::mixer::tests::test_mixer_single_source ... ok +test cache::tests::cache_stats_hit_rate ... ok +test commands::audio::helpers::tests::legacy_device_id_returns_none ... ok +test cache::tests::cache_key_formatting ... ok +test commands::audio::helpers::tests::device_id_kind_mismatch ... ok +test commands::audio_testing::tests::output_test_flag_prevents_concurrent_runs ... ok +test commands::audio_testing::tests::stop_input_test_clears_running_flag ... ok +test commands::audio_testing::tests::stop_input_test_is_idempotent ... ok +test commands::audio_testing::tests::stop_output_test_clears_running_flag ... ok +test commands::audio::helpers::tests::device_id_round_trip ... ok +test commands::audio::helpers::tests::device_id_with_index_is_parsed ... ok +test commands::audio_testing::tests::stop_output_test_is_idempotent ... ok +test commands::identity_tests::tests::complete_auth_login_result_failure ... ok +test commands::identity_tests::tests::complete_auth_login_result_success_with_user ... ok +test commands::identity_tests::tests::complete_auth_login_result_success_without_user ... ok +test commands::identity_tests::tests::get_current_user_result_construction ... ok +test commands::identity_tests::tests::get_current_user_result_local_defaults ... ok +test commands::audio_testing::tests::input_test_flag_prevents_concurrent_runs ... ok +test commands::identity_tests::tests::get_current_user_result_serialization ... ok +test commands::identity_tests::tests::complete_auth_login_result_serialization ... ok +test commands::identity_tests::tests::initiate_auth_login_result_construction ... ok +test commands::identity_tests::tests::initiate_auth_login_result_serialization ... ok +test commands::identity_tests::tests::list_workspaces_result_construction ... ok +test commands::identity_tests::tests::list_workspaces_result_empty ... ok +test commands::identity_tests::tests::logout_provider_list_all ... ok +test commands::identity_tests::tests::logout_provider_list_single ... ok +test commands::identity_tests::tests::logout_result_full_success ... ok +test commands::identity_tests::tests::logout_result_serialization ... ok +test commands::identity_tests::tests::logout_result_with_multiple_revocation_errors ... ok +test commands::identity_tests::tests::logout_result_partial_success ... ok +test commands::identity_tests::tests::logout_result_no_providers ... ok +test commands::identity_tests::tests::switch_workspace_matching_logic ... ok +test commands::identity_tests::tests::switch_workspace_result_failure ... ok +test commands::identity_tests::tests::switch_workspace_result_success ... ok +test cache::memory::tests::set_and_get ... ok +test cache::memory::tests::delete_removes_entry ... ok +test cache::memory::tests::exists_checks_presence ... ok +test commands::identity_tests::tests::workspace_info_construction ... ok +test cache::memory::tests::get_nonexistent_returns_none ... ok +test cache::memory::tests::stats_tracking ... ok +test cache::tests::noop_cache_returns_none ... ok +test commands::identity_tests::tests::workspace_info_serialization ... ok +test commands::identity_tests::tests::workspace_role_serialization ... ok +test commands::playback::audio::tests::trim_audio_buffer_returns_empty_if_start_after_end ... ok +test commands::playback::audio::tests::trim_audio_buffer_returns_original_for_zero_start ... ok +test commands::playback_tests::tests::playback_info_without_highlight ... ok +test commands::playback_tests::tests::playback_info_construction ... ok +test commands::playback_tests::tests::playback_state_default ... ok +test commands::playback_tests::tests::seek_position_with_zero_duration ... ok +test commands::playback_tests::tests::playback_state_serialization ... ok +test commands::playback_tests::tests::seek_position_clamping ... ok +test commands::playback_tests::tests::validate_finite_position ... ok +test commands::playback::audio::tests::trim_audio_buffer_skips_before_start_time ... ok +test commands::recording::tests::bytemuck_f32_to_bytes_handles_empty ... ok +test commands::recording::tests::bytemuck_f32_to_bytes_matches_manual_conversion ... ok +test commands::recording::tests::bytemuck_f32_to_bytes_size_is_correct ... ok +test commands::recording::tests::calculate_rms_handles_silence ... ok +test commands::recording::tests::calculate_rms_unit_signal ... ok +test commands::recording::tests::decode_input_device_id_accepts_input_ids ... ok +test commands::recording::tests::decode_input_device_id_rejects_output_ids ... ok +test commands::recording::tests::downmix_to_mono_averages_channels ... ok +test commands::recording_tests::tests::elapsed_seconds_formatting ... ok +test commands::recording_tests::tests::recording_state_transitions ... ok +test commands::recording::tests::downmix_to_mono_handles_partial_frames ... ok +test config::tests::cache_backend_parsing ... ok +test constants::tests::crypto_sizes_are_correct ... ok +test constants::tests::grpc_timeouts_are_reasonable ... ok +test commands::recording_tests::tests::now_timestamp_is_positive ... ok +test commands::recording::audio::tests::append_spool_chunks_writes_header_and_samples ... ok +test config::tests::default_config_is_valid ... ok +test audio::mixer::tests::test_mixer_buffer_overflow_recorded ... ok +test crypto::tests::crypto_manager_debug_format ... ok +test crypto::tests::crypto_manager_default_matches_new ... ok +test crypto::tests::crypto_manager_new_does_not_initialize ... ok +test audio::loader::tests::load_audio_file_rejects_truncated_payload ... ok +test commands::recording::tests::audio_file_roundtrip_matches_samples ... ok +test audio::loader::tests::load_audio_file_rejects_short_payload ... ok +test crypto::tests::test_different_nonces ... ok +test crypto::tests::test_encrypt_decrypt_with_key ... ok +test crypto::tests::test_hex_encode_decode ... ok +test error::tests::classify_already_connected_as_client ... ok +test error::tests::classify_annotation_not_found ... ok +test error::tests::classify_audio_capture_as_client ... ok +test error::tests::classify_audio_playback_as_client ... ok +test error::tests::classify_connection_error_as_network ... ok +test error::tests::classify_device_not_found_as_client ... ok +test error::tests::classify_encryption_as_client ... ok +test error::tests::classify_integration_not_found ... ok +test error::tests::classify_invalid_input ... ok +test error::tests::classify_grpc_unknown_as_retryable ... ok +test error::tests::classify_grpc_deadline_exceeded ... ok +test error::tests::classify_grpc_resource_exhausted ... ok +test error::tests::classify_grpc_internal ... ok +test error::tests::classify_grpc_unavailable_as_network ... ok +test error::tests::classify_grpc_not_found ... ok +test error::tests::classify_grpc_permission_denied ... ok +test error::tests::classify_grpc_unauthenticated ... ok +test error::tests::classify_grpc_invalid_argument ... ok +test error::tests::classify_invalid_operation ... ok +test error::tests::classify_meeting_not_found ... ok +test error::tests::classify_not_connected_as_client ... ok +test error::tests::classify_stream_as_client ... ok +test error::tests::classify_timeout_error ... ok +test error::tests::error_classification_serialization ... ok +test error::tests::error_classification_without_grpc_status ... ok +test events::tests::grpc_error_classification_is_preserved_in_event_payload ... ok +test grpc::client_tests::tests::annotation_construction ... ok +test grpc::client_tests::tests::annotation_type_from_i32 ... ok +test grpc::client_tests::tests::annotation_type_from_str ... ok +test grpc::client_tests::tests::audio_device_info_construction ... ok +test grpc::client_tests::tests::connection_state_reconnecting_tracks_attempts ... ok +test grpc::client_tests::tests::connection_state_transitions ... ok +test grpc::client_tests::tests::export_format_from_i32 ... ok +test grpc::client_tests::tests::export_format_from_str ... ok +test grpc::client_tests::tests::export_result_empty ... ok +test grpc::client_tests::tests::grpc_client_empty_endpoint ... ok +test grpc::client_tests::tests::grpc_client_endpoint_normalization ... ok +test grpc::client_tests::tests::grpc_client_initial_state ... ok +test grpc::client_tests::tests::job_status_from_i32 ... ok +test grpc::client_tests::tests::meeting_info_new ... ok +test grpc::client_tests::tests::meeting_info_stopped ... ok +test grpc::client_tests::tests::meeting_new ... ok +test grpc::client_tests::tests::meeting_state_from_i32 ... ok +test grpc::client_tests::tests::meeting_state_serialization ... ok +test grpc::client_tests::tests::priority_from_i32 ... ok +test grpc::client_tests::tests::meeting_to_info ... ok +test grpc::client_tests::tests::segment_construction ... ok +test grpc::client_tests::tests::server_info_default ... ok +test grpc::client_tests::tests::meeting_stopped ... ok +test grpc::client_tests::tests::timestamped_audio_construction ... ok +test grpc::client_tests::tests::update_type_from_i32 ... ok +test grpc::client_tests::tests::word_timing_construction ... ok +test grpc::proto_compliance_tests::tests::all_types_instantiable ... ok +test grpc::proto_compliance_tests::tests::annotation_type_count ... ok +test grpc::proto_compliance_tests::tests::annotation_type_ordinals ... ok +test grpc::proto_compliance_tests::tests::annotation_type_roundtrip ... ok +test grpc::proto_compliance_tests::tests::annotation_type_serialization ... ok +test grpc::proto_compliance_tests::tests::annotation_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::audio_chunk_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::action_item_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::diarization_job_status_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::export_format_count ... ok +test grpc::proto_compliance_tests::tests::export_format_ordinals ... ok +test grpc::proto_compliance_tests::tests::export_format_roundtrip ... ok +test grpc::proto_compliance_tests::tests::export_format_serialization ... ok +test grpc::proto_compliance_tests::tests::export_result_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::job_status_count ... ok +test grpc::proto_compliance_tests::tests::job_status_ordinals ... ok +test grpc::proto_compliance_tests::tests::job_status_roundtrip ... ok +test grpc::proto_compliance_tests::tests::job_status_serialization ... ok +test grpc::proto_compliance_tests::tests::key_point_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::list_meetings_response_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::meeting_state_count ... ok +test grpc::proto_compliance_tests::tests::meeting_state_roundtrip ... ok +test grpc::proto_compliance_tests::tests::meeting_state_serialization ... ok +test grpc::proto_compliance_tests::tests::priority_count ... ok +test grpc::proto_compliance_tests::tests::priority_ordinals ... ok +test grpc::proto_compliance_tests::tests::meeting_state_ordinals ... ok +test grpc::proto_compliance_tests::tests::priority_roundtrip ... ok +test grpc::proto_compliance_tests::tests::priority_serialization ... ok +test grpc::proto_compliance_tests::tests::rename_speaker_result_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::summary_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::transcript_update_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::segment_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::server_info_fields_match_proto ... ok +test grpc::proto_compliance_tests::tests::update_type_serialization ... ok +test grpc::proto_compliance_tests::tests::update_type_roundtrip ... ok +test grpc::proto_compliance_tests::tests::update_type_ordinals ... ok +test grpc::streaming::activity::tests::test_activity_atomic_updates_are_visible ... ok +test grpc::streaming::activity::tests::test_activity_resumption::case_1_resume_after_brief_inactivity ... ok +test grpc::proto_compliance_tests::tests::update_type_count ... ok +test grpc::streaming::activity::tests::test_activity_resumption::case_3_partial_resume ... ok +test grpc::streaming::activity::tests::test_activity_timestamp_monotonic ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_1_short_timeout_active ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_2_short_timeout_inactive ... ok +test grpc::proto_compliance_tests::tests::word_timing_fields_match_proto ... ok +test grpc::streaming::activity::tests::test_activity_resumption::case_2_resume_after_long_inactivity ... ok +test commands::recording_tests::tests::now_timestamp_is_increasing ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_5_long_timeout_active ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_3_medium_timeout_active ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_4_medium_timeout_inactive ... ok +test grpc::streaming::activity::tests::test_edge_cases::case_2_zero_inactivity ... ok +test grpc::streaming::activity::tests::test_edge_cases::case_3_exact_boundary ... ok +test grpc::streaming::activity::tests::test_edge_cases::case_1_zero_timeout ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_1_well_before_threshold ... ok +test grpc::streaming::activity::tests::test_edge_cases::case_4_one_ms_before ... ok +test grpc::streaming::activity::tests::test_configurable_inactivity_timeout::case_6_long_timeout_inactive ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_3_four_minutes_inactive ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_2_one_minute_inactive ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_5_exactly_at_threshold ... ok +test grpc::streaming::activity::tests::test_edge_cases::case_5_one_ms_after ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_4_just_before_threshold ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_7_well_past_threshold ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_1_just_started ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_3_one_hour ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_4_two_hours ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_5_three_hours ... ok +test grpc::streaming::activity::tests::test_long_meeting_with_breaks ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_2_fifteen_minutes ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_6_just_before_max ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_7_exactly_at_max ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_8_just_after_max ... ok +test grpc::streaming::activity::tests::test_max_duration_threshold::case_9_five_hours ... ok +test grpc::streaming::activity::tests::test_saturating_sub_handles_clock_skew ... ok +test grpc::streaming::activity::tests::test_inactivity_threshold_behavior::case_6_just_after_threshold ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_1_standup ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_2_sync ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_3_planning ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_4_workshop ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_5_half_day ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_6_long_workshop ... ok +test grpc::streaming::activity::tests::test_typical_meeting_durations::case_7_at_limit ... ok +test grpc::streaming::converters::tests::convert_segment_preserves_structure ... ok +test grpc::streaming::converters::tests::convert_word_preserves_fields ... ok +test grpc::streaming::manager::tests::test_stream_state_info_variants::case_1_idle ... ok +test grpc::streaming::manager::tests::test_stream_state_info_variants::case_3_active ... ok +test grpc::streaming::manager::tests::test_stream_state_info_variants::case_2_starting ... ok +test grpc::streaming::manager::tests::test_stream_state_info_variants::case_4_stopping ... ok +test helpers::tests::format_duration_long ... ok +test helpers::tests::clamp_works_correctly ... ok +test helpers::tests::format_duration_short ... ok +test helpers::tests::new_id_is_valid_uuid ... ok +test helpers::tests::new_id_is_unique ... ok +test helpers::tests::normalize_db_level_handles_zero_range ... ok +test helpers::tests::normalize_db_level_works ... ok +test helpers::tests::now_timestamp_is_positive ... ok +test helpers::tests::sanitize_filename_removes_invalid_chars ... ok +test identity::tests::identity_store_new_does_not_load ... ok +test identity::tests::stored_identity_default_is_local ... ok +test identity::tests::stored_identity_from_auth_is_not_local ... ok +test oauth_loopback::tests::test_parse_callback_error ... ok +test oauth_loopback::tests::test_parse_callback_missing_code ... ok +test oauth_loopback::tests::test_parse_callback_success ... ok +test oauth_loopback::tests::test_parse_callback_with_encoded_values ... ok +test oauth_loopback::tests::test_urlencoding_decode ... ok +test oauth_loopback::tests::test_urlencoding_decode_multibyte_utf8 ... ok +test state::shutdown::tests::double_shutdown_is_idempotent ... ok +test state::shutdown::tests::drop_triggers_shutdown ... ok +test state::shutdown::tests::shutdown_manager_signals_cancellation ... ok +test state::state_tests::audio_config_default ... ok +test state::state_tests::playback_info_without_highlight ... ok +test state::state_tests::playback_state_copy ... ok +test state::state_tests::playback_state_default ... ok +test state::state_tests::audio_config_uses_saved_device_ids ... ok +test state::state_tests::playback_info_construction ... ok +test state::state_tests::playback_state_equality ... ok +test state::state_tests::playback_state_serialization ... ok +test state::state_tests::trigger_action_serialization ... ok +test state::state_tests::trigger_decision_auto_start ... ok +test state::state_tests::trigger_signal_construction ... ok +test state::state_tests::trigger_decision_construction ... ok +test state::state_tests::trigger_source_serialization ... ok +test state::state_tests::trigger_state_add_dismissed_enforces_bounds ... ok +test state::state_tests::trigger_decision_serialization ... ok +test state::state_tests::trigger_signal_without_app_name ... ok +test state::state_tests::trigger_state_add_dismissed_prevents_duplicates ... ok +test triggers::tests::test_is_meeting_app ... ok +test triggers::tests::test_snooze ... ok +test triggers::tests::test_parse_linux_exec_command ... ok +test triggers::tests::test_snooze_invalid_duration ... ok +test triggers::tests::test_index_desktop_file_tracks_exec_and_wm_class ... ok +test commands::recording::session::processing::tests::smoke_long_recording_caps_buffer_samples ... ok +test grpc::client_tests::tests::identity_interceptor_omits_auth_when_not_authenticated ... ok +test grpc::client_tests::tests::identity_interceptor_injects_required_headers ... ok +test grpc::client_tests::tests::identity_interceptor_debug_output ... ok +test grpc::client_tests::tests::identity_interceptor_uses_local_defaults ... ok +test grpc::client_tests::tests::identity_interceptor_generates_unique_request_ids ... ok +test identity::tests::identity_manager_provides_defaults ... ok +test commands::recording_tests::tests::elapsed_seconds_calculation ... ok + +test result: ok. 323 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s + + Running unittests src/main.rs (target/debug/deps/noteflow_tauri-8a72594d19768762) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/async_robustness.rs (target/debug/deps/async_robustness-987a75be37133bf8) + +running 11 tests +test cancellation_token_child_inherits_cancellation ... ok +test receiver_handles_sender_closure ... ok +test async_writes_are_serialized ... ok +test concurrent_async_reads_dont_block ... ok +test spawned_tasks_can_be_awaited ... ok +test graceful_shutdown_sequence ... ok +test atomic_state_transition_prevents_double_operation ... ok +test multiple_tasks_share_cancellation_token ... ok +test background_task_respects_cancellation ... ok +test dropped_handle_task_continues ... ok +test atomic_shutdown_flag_works ... ok + +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s + + Running tests/device_integration.rs (target/debug/deps/device_integration-9a9fb58e27a1b7f0) + +running 4 tests +test input_device_available ... ignored, requires physical audio devices +test input_device_supports_requested_format ... ignored, requires physical audio devices +test output_device_available ... ignored, requires physical audio devices +test output_device_supports_requested_format ... ignored, requires physical audio devices + +test result: ok. 0 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/grpc_integration.rs (target/debug/deps/grpc_integration-f7f0512454ddbfdc) + +running 20 tests +test integration::calendar_operations ... ignored, integration test; requires running server +test integration::cloud_consent_operations ... ignored, integration test; requires running server +test integration::cloud_summary_reconnect_with_jitter ... ignored, integration test; requires running server +test integration::connect_fails_gracefully_with_invalid_server ... ignored, integration test; requires running server +test integration::connect_with_none_uses_cached_endpoint ... ignored, integration test; requires running server +test integration::create_and_delete_meeting_roundtrip ... ignored, integration test; requires running server +test integration::diarization_operations ... ignored, integration test; requires running server +test integration::diarization_refinement_smoke ... ignored, integration test; requires running server +test integration::full_meeting_lifecycle ... ignored, integration test; requires running server +test integration::get_server_info_returns_valid_response ... ignored, integration test; requires running server +test integration::list_meetings_returns_valid_response ... ignored, integration test; requires running server +test integration::observability_operations ... ignored, integration test; requires running server +test integration::preferences_roundtrip ... ignored, integration test; requires running server +test integration::project_operations ... ignored, integration test; requires running server +test integration::real_audio_streaming_e2e ... ignored, integration test; requires running server +test integration::real_audio_streaming_extreme_duration ... ignored, integration test; requires running server +test integration::server_is_reachable ... ignored, integration test; requires running server +test integration::streaming_rejects_format_change_mid_stream ... ignored, integration test; requires running server +test integration::user_integrations_operations ... ignored, integration test; requires running server +test integration::webhook_crud_operations ... ignored, integration test; requires running server + +test result: ok. 0 passed; 0 failed; 20 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/harness.rs (target/debug/deps/harness-c82ab06386f14f2a) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/robustness.rs (target/debug/deps/robustness-5daa78ffbb362da8) + +running 16 tests +test annotation_time_validation ... ok +test crypto_constants_match_aes_gcm_requirements ... ok +test event_names_follow_convention ... ok +test format_duration_handles_edge_cases ... ok +test normalize_db_level_clamps_correctly ... ok +test pagination_validation_clamps_values ... ok +test playback_state_wrapper_default ... ok +test sanitize_filename_removes_dangerous_chars ... ok +test playback_state_wrapper_reset_clears_state ... ok +test sort_order_validation ... ok +test timeout_constants_are_reasonable ... ok +test trigger_source_serializes_correctly ... ok +test trigger_state_dismissed_triggers_deduplicates ... ok +test trigger_state_dismissed_triggers_bounded ... ok +test atomic_counter_is_thread_safe ... ok +test trigger_state_snooze_works ... ok + +test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.10s + + Running tests/setup_spawn_tests.rs (target/debug/deps/setup_spawn_tests-da5dcd9ab0b2f89d) + +running 7 tests +test spawn_pattern_works_without_existing_runtime ... ok +test rapid_spawn_shutdown_is_stable ... ok +test local_runtime_in_spawned_thread_works ... ok +test broadcast_channel_works_with_local_runtime ... ok +test simulated_event_emitter_pattern ... ok +test tokio_select_works_in_local_runtime ... ok +test multiple_threads_with_local_runtimes ... ok + +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s + + Doc-tests noteflow_lib + +running 1 test +test src/crypto/mod.rs - crypto::CryptoManager (line 227) ... ignored + +test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s + diff --git a/client/src-tauri/src/audio/capture.rs b/client/src-tauri/src/audio/capture.rs index ba5cdc1..4d47512 100644 --- a/client/src-tauri/src/audio/capture.rs +++ b/client/src-tauri/src/audio/capture.rs @@ -75,7 +75,9 @@ impl AudioCapture { // Send complete buffers while buffer.len() >= buffer_size { let chunk: Vec = buffer.drain(..buffer_size).collect(); - let _ = audio_tx.blocking_send(chunk); + if let Err(e) = audio_tx.blocking_send(chunk) { + tracing::warn!("Audio buffer full or channel closed: {}", e); + } } }, move |err| { @@ -116,15 +118,20 @@ pub fn calculate_rms(samples: &[f32]) -> f32 { } /// Calculate RMS for i16 audio samples, normalized to f32 range [-1.0, 1.0]. +/// +/// Uses 32768.0 for normalization to handle the asymmetric i16 range (-32768 to 32767) +/// symmetrically, ensuring values stay within [-1.0, 1.0]. pub fn calculate_rms_i16(samples: &[i16]) -> f32 { if samples.is_empty() { return 0.0; } + const I16_NORMALIZATION_FACTOR: f32 = 32768.0; + let sum_squares: f32 = samples .iter() .map(|&s| { - let normalized = s as f32 / i16::MAX as f32; + let normalized = s as f32 / I16_NORMALIZATION_FACTOR; normalized * normalized }) .sum(); @@ -158,6 +165,130 @@ pub fn rms_to_db(rms: f32) -> f32 { // Note: Use helpers::normalize_db_level() for normalizing dB to 0.0-1.0 range +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_COUNT: usize = 100; + const SILENCE_SAMPLE: f32 = 0.0; + const UNIT_SAMPLE: f32 = 1.0; + const QUIET_SAMPLE: f32 = 0.001; + const LOW_SAMPLE: f32 = 0.01; + const BOOST_SAMPLE: f32 = 0.02; + const MIDPOINT_U16: u16 = 32768; + const NEAR_ZERO_THRESHOLD: f32 = 0.001; + + #[test] + fn calculate_rms_empty_returns_zero() { + assert_eq!(calculate_rms(&[]), 0.0); + } + + #[test] + fn calculate_rms_silence_returns_zero() { + let samples = vec![SILENCE_SAMPLE; SAMPLE_COUNT]; + assert_eq!(calculate_rms(&samples), 0.0); + } + + #[test] + fn calculate_rms_unit_signal() { + let samples = vec![UNIT_SAMPLE; SAMPLE_COUNT]; + assert!((calculate_rms(&samples) - UNIT_SAMPLE).abs() < f32::EPSILON); + } + + #[test] + fn calculate_rms_i16_empty_returns_zero() { + assert_eq!(calculate_rms_i16(&[]), 0.0); + } + + #[test] + fn calculate_rms_i16_min_value_within_bounds() { + let samples = vec![i16::MIN; SAMPLE_COUNT]; + let rms = calculate_rms_i16(&samples); + assert!(rms <= 1.0, "RMS {} should be <= 1.0", rms); + } + + #[test] + fn calculate_rms_u16_empty_returns_zero() { + assert_eq!(calculate_rms_u16(&[]), 0.0); + } + + #[test] + fn calculate_rms_u16_midpoint_near_zero() { + let samples = vec![MIDPOINT_U16; SAMPLE_COUNT]; + let rms = calculate_rms_u16(&samples); + assert!(rms < NEAR_ZERO_THRESHOLD, "Midpoint RMS {} should be near 0", rms); + } + + #[test] + fn rms_to_db_silence_returns_floor() { + assert_eq!(rms_to_db(0.0), audio_config::MIN_DB_LEVEL); + } + + #[test] + fn rms_to_db_negative_returns_floor() { + assert_eq!(rms_to_db(-0.5), audio_config::MIN_DB_LEVEL); + } + + #[test] + fn rms_to_db_unit_signal_is_zero() { + assert!((rms_to_db(1.0) - 0.0).abs() < f32::EPSILON); + } + + #[test] + fn soft_clip_linear_region() { + assert!((soft_clip(0.3) - 0.3).abs() < f32::EPSILON); + assert!((soft_clip(-0.3) - (-0.3)).abs() < f32::EPSILON); + } + + #[test] + fn soft_clip_saturation_region() { + let result = soft_clip(2.0); + assert!(result < 1.0, "Clipped value {} should be < 1.0", result); + assert!(result > 0.5, "Clipped value {} should be > 0.5", result); + } + + #[test] + fn soft_clip_negative_saturation() { + let result = soft_clip(-2.0); + assert!(result > -1.0, "Clipped value {} should be > -1.0", result); + assert!(result < -0.5, "Clipped value {} should be < -0.5", result); + } + + #[test] + fn normalize_for_asr_empty_returns_unity_gain() { + let mut samples: Vec = vec![]; + let gain = normalize_for_asr(&mut samples); + assert_eq!(gain, 1.0); + } + + #[test] + fn normalize_for_asr_silence_returns_unity_gain() { + let mut samples = vec![QUIET_SAMPLE; SAMPLE_COUNT]; + let gain = normalize_for_asr(&mut samples); + assert_eq!(gain, 1.0); + } + + #[test] + fn normalize_for_asr_boosts_quiet_audio() { + let mut samples = vec![LOW_SAMPLE; SAMPLE_COUNT]; + let gain = normalize_for_asr(&mut samples); + assert!(gain > 1.0, "Gain {} should be > 1.0 for quiet audio", gain); + } + + #[test] + fn normalize_for_asr_respects_max_gain() { + let mut samples = vec![QUIET_SAMPLE; SAMPLE_COUNT]; + samples[0] = BOOST_SAMPLE; + let gain = normalize_for_asr(&mut samples); + assert!( + gain <= MAX_GAIN, + "Gain {} should be <= MAX_GAIN {}", + gain, + MAX_GAIN + ); + } +} + // ============================================================================ // Audio Normalization for ASR // Boosts quiet audio (e.g., system loopback) to improve speech recognition. diff --git a/client/src-tauri/src/commands/analytics.rs b/client/src-tauri/src/commands/analytics.rs index 2ec261b..b1e9ade 100644 --- a/client/src-tauri/src/commands/analytics.rs +++ b/client/src-tauri/src/commands/analytics.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use tauri::State; use crate::error::Result; -use crate::grpc::types::analytics::{AnalyticsOverview, ListSpeakerStatsResult}; +use crate::grpc::types::analytics::{AnalyticsOverview, EntityAnalytics, ListSpeakerStatsResult}; use crate::state::AppState; #[tauri::command(rename_all = "snake_case")] @@ -33,3 +33,18 @@ pub async fn list_speaker_stats( .list_speaker_stats(start_time, end_time, project_id, project_ids) .await } + +#[tauri::command(rename_all = "snake_case")] +pub async fn get_entity_analytics( + state: State<'_, Arc>, + start_time: f64, + end_time: f64, + project_id: Option, + project_ids: Option>, + top_limit: Option, +) -> Result { + state + .grpc_client + .get_entity_analytics(start_time, end_time, project_id, project_ids, top_limit) + .await +} diff --git a/client/src-tauri/src/commands/meeting.rs b/client/src-tauri/src/commands/meeting.rs index 27e3eea..0e958e1 100644 --- a/client/src-tauri/src/commands/meeting.rs +++ b/client/src-tauri/src/commands/meeting.rs @@ -25,6 +25,7 @@ pub async fn create_meeting( /// List meetings with optional filters. #[tauri::command(rename_all = "snake_case")] +#[allow(clippy::too_many_arguments)] pub async fn list_meetings( state: State<'_, Arc>, states: Option>, @@ -33,16 +34,15 @@ pub async fn list_meetings( sort_order: Option, project_id: Option, project_ids: Option>, + include_segments: Option, ) -> Result { - // Validate and clamp pagination parameters to non-negative values let validated_limit = limit .unwrap_or(pagination::DEFAULT_LIMIT) .clamp(pagination::MIN_LIMIT, pagination::MAX_MEETINGS_LIMIT); let validated_offset = offset.unwrap_or(0).max(0); - // Sort order: 1 = newest first (desc), -1 = oldest first (asc) let validated_sort = match sort_order.unwrap_or(1) { -1 => -1, - _ => 1, // Default to newest first for any invalid value + _ => 1, }; state @@ -54,6 +54,7 @@ pub async fn list_meetings( validated_sort, project_id, project_ids.unwrap_or_default(), + include_segments.unwrap_or(false), ) .await } diff --git a/client/src-tauri/src/grpc/client/analytics.rs b/client/src-tauri/src/grpc/client/analytics.rs index 7755798..bd177ac 100644 --- a/client/src-tauri/src/grpc/client/analytics.rs +++ b/client/src-tauri/src/grpc/client/analytics.rs @@ -1,7 +1,8 @@ use crate::error::Result; use crate::grpc::noteflow as pb; use crate::grpc::types::analytics::{ - AnalyticsOverview, DailyMeetingStats, ListSpeakerStatsResult, SpeakerStat, + AnalyticsOverview, DailyMeetingStats, EntityAnalytics, EntityCategoryStat, + ListSpeakerStatsResult, SpeakerStat, TopEntity, }; use super::core::GrpcClient; @@ -57,6 +58,42 @@ impl GrpcClient { speakers: response.speakers.into_iter().map(map_speaker_stat).collect(), }) } + + pub async fn get_entity_analytics( + &self, + start_time: f64, + end_time: f64, + project_id: Option, + project_ids: Option>, + top_limit: Option, + ) -> Result { + let mut client = self.get_client()?; + let response = client + .get_entity_analytics(pb::GetEntityAnalyticsRequest { + start_time, + end_time, + project_id, + project_ids: project_ids.unwrap_or_default(), + top_limit: top_limit.unwrap_or(20), + }) + .await? + .into_inner(); + + Ok(EntityAnalytics { + by_category: response + .by_category + .into_iter() + .map(map_entity_category_stat) + .collect(), + top_entities: response + .top_entities + .into_iter() + .map(map_top_entity) + .collect(), + total_entities: response.total_entities, + total_mentions: response.total_mentions, + }) + } } fn map_daily_stats(proto: pb::DailyMeetingStatsProto) -> DailyMeetingStats { @@ -78,3 +115,20 @@ fn map_speaker_stat(proto: pb::SpeakerStatProto) -> SpeakerStat { avg_confidence: proto.avg_confidence, } } + +fn map_entity_category_stat(proto: pb::EntityCategoryStatProto) -> EntityCategoryStat { + EntityCategoryStat { + category: proto.category, + count: proto.count, + total_mentions: proto.total_mentions, + } +} + +fn map_top_entity(proto: pb::TopEntityProto) -> TopEntity { + TopEntity { + text: proto.text, + category: proto.category, + mention_count: proto.mention_count, + meeting_count: proto.meeting_count, + } +} diff --git a/client/src-tauri/src/grpc/client/meetings.rs b/client/src-tauri/src/grpc/client/meetings.rs index b5a0fa9..435d2d4 100644 --- a/client/src-tauri/src/grpc/client/meetings.rs +++ b/client/src-tauri/src/grpc/client/meetings.rs @@ -69,6 +69,7 @@ impl GrpcClient { /// List meetings with filters. #[instrument(skip(self))] + #[allow(clippy::too_many_arguments)] pub async fn list_meetings( &self, states: Vec, @@ -77,6 +78,7 @@ impl GrpcClient { sort_order: i32, project_id: Option, project_ids: Vec, + include_segments: bool, ) -> Result { let mut client = self.get_client()?; let response = client @@ -87,6 +89,7 @@ impl GrpcClient { sort_order, project_id, project_ids, + include_segments, }) .await? .into_inner(); diff --git a/client/src-tauri/src/grpc/noteflow.rs b/client/src-tauri/src/grpc/noteflow.rs index abb15e0..5bd82f4 100644 --- a/client/src-tauri/src/grpc/noteflow.rs +++ b/client/src-tauri/src/grpc/noteflow.rs @@ -185,6 +185,9 @@ pub struct ListMeetingsRequest { /// Optional project filter for multiple projects (overrides project_id when provided) #[prost(string, repeated, tag = "6")] pub project_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Whether to include full transcript segments (default: false) + #[prost(bool, tag = "7")] + pub include_segments: bool, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListMeetingsResponse { @@ -2524,6 +2527,50 @@ pub struct ListSpeakerStatsResponse { #[prost(message, repeated, tag = "1")] pub speakers: ::prost::alloc::vec::Vec, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EntityCategoryStatProto { + #[prost(string, tag = "1")] + pub category: ::prost::alloc::string::String, + #[prost(int32, tag = "2")] + pub count: i32, + #[prost(int32, tag = "3")] + pub total_mentions: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TopEntityProto { + #[prost(string, tag = "1")] + pub text: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub category: ::prost::alloc::string::String, + #[prost(int32, tag = "3")] + pub mention_count: i32, + #[prost(int32, tag = "4")] + pub meeting_count: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEntityAnalyticsRequest { + #[prost(double, tag = "1")] + pub start_time: f64, + #[prost(double, tag = "2")] + pub end_time: f64, + #[prost(string, optional, tag = "3")] + pub project_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "4")] + pub project_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(int32, tag = "5")] + pub top_limit: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEntityAnalyticsResponse { + #[prost(message, repeated, tag = "1")] + pub by_category: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "2")] + pub top_entities: ::prost::alloc::vec::Vec, + #[prost(int32, tag = "3")] + pub total_entities: i32, + #[prost(int32, tag = "4")] + pub total_mentions: i32, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum UpdateType { @@ -5190,6 +5237,32 @@ pub mod note_flow_service_client { .insert(GrpcMethod::new("noteflow.NoteFlowService", "ListSpeakerStats")); self.inner.unary(req, path, codec).await } + pub async fn get_entity_analytics( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/noteflow.NoteFlowService/GetEntityAnalytics", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("noteflow.NoteFlowService", "GetEntityAnalytics"), + ); + self.inner.unary(req, path, codec).await + } /// Project membership management (Sprint 18) pub async fn add_project_member( &mut self, diff --git a/client/src-tauri/src/grpc/types/analytics.rs b/client/src-tauri/src/grpc/types/analytics.rs index 9d98697..027c855 100644 --- a/client/src-tauri/src/grpc/types/analytics.rs +++ b/client/src-tauri/src/grpc/types/analytics.rs @@ -32,3 +32,26 @@ pub struct SpeakerStat { pub struct ListSpeakerStatsResult { pub speakers: Vec, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EntityCategoryStat { + pub category: String, + pub count: i32, + pub total_mentions: i32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TopEntity { + pub text: String, + pub category: String, + pub mention_count: i32, + pub meeting_count: i32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EntityAnalytics { + pub by_category: Vec, + pub top_entities: Vec, + pub total_entities: i32, + pub total_mentions: i32, +} diff --git a/client/src-tauri/src/lib.rs b/client/src-tauri/src/lib.rs index c81779d..ab278e5 100644 --- a/client/src-tauri/src/lib.rs +++ b/client/src-tauri/src/lib.rs @@ -199,9 +199,10 @@ macro_rules! app_invoke_handler { // Tasks (2 commands) - Bugfinder Sprint commands::list_tasks, commands::update_task, - // Analytics (2 commands) - Bugfinder Sprint + // Analytics (3 commands) - Bugfinder Sprint commands::get_analytics_overview, commands::list_speaker_stats, + commands::get_entity_analytics, ] }; } diff --git a/client/src-tauri/tests/grpc_integration.rs b/client/src-tauri/tests/grpc_integration.rs index 2c0499a..20e84ca 100644 --- a/client/src-tauri/tests/grpc_integration.rs +++ b/client/src-tauri/tests/grpc_integration.rs @@ -412,8 +412,8 @@ mod integration { .await .expect("Failed to connect"); - // list_meetings(states, limit, offset, sort_order, project_id, project_ids) - let result = client.list_meetings(vec![], 10, 0, 0, None, vec![]).await; + // list_meetings(states, limit, offset, sort_order, project_id, project_ids, include_segments) + let result = client.list_meetings(vec![], 10, 0, 0, None, vec![], false).await; assert!( result.is_ok(), "Failed to list meetings: {:?}", diff --git a/client/src/api/adapters/cached/apps.test.ts b/client/src/api/adapters/cached/apps.test.ts new file mode 100644 index 0000000..8dbb824 --- /dev/null +++ b/client/src/api/adapters/cached/apps.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; + +import { cachedAppsAPI } from './apps'; + +describe('cachedAppsAPI', () => { + it('returns empty installed apps with paging defaults', async () => { + const response = await cachedAppsAPI.listInstalledApps(); + expect(response.apps).toEqual([]); + expect(response.total).toBe(0); + expect(response.page).toBe(0); + expect(response.page_size).toBe(50); + expect(response.has_more).toBe(false); + + const responseWithOptions = await cachedAppsAPI.listInstalledApps({ page: 2, pageSize: 10 }); + expect(responseWithOptions.page).toBe(2); + expect(responseWithOptions.page_size).toBe(10); + }); + + it('rejects cache invalidation in cached mode', async () => { + await expect(cachedAppsAPI.invalidateAppCache()).rejects.toThrow('Cached read-only mode'); + }); +}); diff --git a/client/src/api/adapters/cached/audio.test.ts b/client/src/api/adapters/cached/audio.test.ts new file mode 100644 index 0000000..d1a7cdc --- /dev/null +++ b/client/src/api/adapters/cached/audio.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; + +import { cachedAudioAPI } from './audio'; + +describe('cachedAudioAPI', () => { + it('returns empty devices and default environment info', async () => { + await expect(cachedAudioAPI.listAudioDevices()).resolves.toEqual([]); + await expect(cachedAudioAPI.getDefaultAudioDevice(true)).resolves.toBeNull(); + + await expect(cachedAudioAPI.checkTestEnvironment()).resolves.toEqual({ + hasInputDevices: false, + hasVirtualDevice: false, + inputDevices: [], + isServerConnected: false, + canRunAudioTests: false, + }); + }); + + it('rejects mutating audio operations', async () => { + await expect(cachedAudioAPI.selectAudioDevice('device', true)).rejects.toThrow( + 'Cached read-only mode' + ); + await expect( + cachedAudioAPI.injectTestAudio('meeting', { + isInput: true, + volumeDb: -12, + durationSeconds: 1, + }) + ).rejects.toThrow('Cached read-only mode'); + await expect(cachedAudioAPI.injectTestTone('meeting', 440, 1, 16000)).rejects.toThrow( + 'Cached read-only mode' + ); + }); +}); diff --git a/client/src/api/adapters/cached/projects.test.ts b/client/src/api/adapters/cached/projects.test.ts new file mode 100644 index 0000000..04ecdc1 --- /dev/null +++ b/client/src/api/adapters/cached/projects.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { cachedProjectsAPI } from './projects'; +import { offlineProjects } from './defaults'; + +describe('cachedProjectsAPI', () => { + it('returns projects from offline cache and handles missing data', async () => { + const project = offlineProjects.projects[0]; + const workspaceId = project?.workspace_id ?? 'workspace-1'; + + const list = await cachedProjectsAPI.listProjects({ workspace_id: workspaceId }); + expect(list.total_count).toBe(list.projects.length); + + if (project) { + const fetched = await cachedProjectsAPI.getProject({ project_id: project.id }); + expect(fetched.id).toBe(project.id); + + const bySlug = await cachedProjectsAPI.getProjectBySlug({ + workspace_id: project.workspace_id, + slug: project.slug, + }); + expect(bySlug.id).toBe(project.id); + } + + await expect( + cachedProjectsAPI.getProject({ project_id: 'missing' }) + ).rejects.toThrow('Project not available in offline cache.'); + + await expect( + cachedProjectsAPI.getProjectBySlug({ + workspace_id: 'missing', + slug: 'missing', + }) + ).rejects.toThrow('Project not available in offline cache.'); + }); + + it('returns active project or throws when none available', async () => { + const workspaceId = offlineProjects.projects[0]?.workspace_id ?? 'workspace-1'; + + const active = await cachedProjectsAPI.getActiveProject({ workspace_id: workspaceId }); + expect(active.project_id).toBeDefined(); + expect(active.project).toBeDefined(); + + vi.resetModules(); + vi.doMock('./defaults', () => ({ + offlineProjects: { projects: [], total_count: 0 }, + })); + const { cachedProjectsAPI: emptyAPI } = await import('./projects'); + + await expect(emptyAPI.getActiveProject({ workspace_id: workspaceId })).rejects.toThrow( + 'No project available in offline cache.' + ); + }); + + it('rejects write operations and provides empty members', async () => { + await expect( + cachedProjectsAPI.createProject({ workspace_id: 'w1', name: 'x' }) + ).rejects.toThrow( + 'Cached read-only mode' + ); + await expect(cachedProjectsAPI.updateProject({ project_id: 'p1', name: 'y' })).rejects.toThrow( + 'Cached read-only mode' + ); + await expect(cachedProjectsAPI.archiveProject('p1')).rejects.toThrow('Cached read-only mode'); + await expect(cachedProjectsAPI.restoreProject('p1')).rejects.toThrow('Cached read-only mode'); + await expect(cachedProjectsAPI.deleteProject('p1')).rejects.toThrow('Cached read-only mode'); + await expect( + cachedProjectsAPI.addProjectMember({ project_id: 'p1', user_id: 'u1', role: 'viewer' }) + ).rejects.toThrow('Cached read-only mode'); + await expect( + cachedProjectsAPI.updateProjectMemberRole({ + project_id: 'p1', + user_id: 'u1', + role: 'editor', + }) + ).rejects.toThrow('Cached read-only mode'); + await expect( + cachedProjectsAPI.removeProjectMember({ project_id: 'p1', user_id: 'u1' }) + ).rejects.toThrow('Cached read-only mode'); + + await expect( + cachedProjectsAPI.listProjectMembers({ project_id: 'p1', limit: 20, offset: 0 }) + ).resolves.toEqual({ members: [], total_count: 0 }); + + await expect( + cachedProjectsAPI.setActiveProject({ workspace_id: 'w1', project_id: 'p1' }) + ).resolves.toBeUndefined(); + }); +}); diff --git a/client/src/api/adapters/cached/templates.test.ts b/client/src/api/adapters/cached/templates.test.ts new file mode 100644 index 0000000..81804a6 --- /dev/null +++ b/client/src/api/adapters/cached/templates.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; + +import { cachedTemplatesAPI } from './templates'; + +describe('cachedTemplatesAPI', () => { + it('returns empty lists and consent status', async () => { + const templates = await cachedTemplatesAPI.listSummarizationTemplates({ workspace_id: 'w1' }); + expect(templates.total_count).toBe(0); + + const versions = await cachedTemplatesAPI.listSummarizationTemplateVersions({ + template_id: 't1', + }); + expect(versions.versions).toEqual([]); + + const consent = await cachedTemplatesAPI.getCloudConsentStatus(); + expect(consent.consentGranted).toBe(false); + }); + + it('rejects template mutations and throws for get', async () => { + await expect( + cachedTemplatesAPI.getSummarizationTemplate({ template_id: 't1' }) + ).rejects.toThrow('Summarization templates are unavailable in offline mode.'); + + await expect( + cachedTemplatesAPI.createSummarizationTemplate({ workspace_id: 'w1', name: 'x' }) + ).rejects.toThrow('Cached read-only mode'); + await expect( + cachedTemplatesAPI.updateSummarizationTemplate({ template_id: 't1', name: 'x' }) + ).rejects.toThrow('Cached read-only mode'); + await expect( + cachedTemplatesAPI.archiveSummarizationTemplate({ template_id: 't1' }) + ).rejects.toThrow('Cached read-only mode'); + await expect( + cachedTemplatesAPI.restoreSummarizationTemplateVersion({ template_version_id: 'v1' }) + ).rejects.toThrow('Cached read-only mode'); + await expect(cachedTemplatesAPI.grantCloudConsent()).rejects.toThrow('Cached read-only mode'); + await expect(cachedTemplatesAPI.revokeCloudConsent()).rejects.toThrow('Cached read-only mode'); + }); +}); diff --git a/client/src/api/adapters/cached/triggers.test.ts b/client/src/api/adapters/cached/triggers.test.ts new file mode 100644 index 0000000..cd57ac2 --- /dev/null +++ b/client/src/api/adapters/cached/triggers.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; + +import { cachedTriggersAPI } from './triggers'; + +describe('cachedTriggersAPI', () => { + it('returns trigger status from offline cache', async () => { + const status = await cachedTriggersAPI.getTriggerStatus(); + expect(status).toEqual({ enabled: false, is_snoozed: false }); + }); + + it('rejects trigger mutations in cached mode', async () => { + await expect(cachedTriggersAPI.setTriggerEnabled(true)).rejects.toThrow('Cached read-only mode'); + await expect(cachedTriggersAPI.snoozeTriggers(10)).rejects.toThrow('Cached read-only mode'); + await expect(cachedTriggersAPI.resetSnooze()).rejects.toThrow('Cached read-only mode'); + await expect(cachedTriggersAPI.dismissTrigger()).rejects.toThrow('Cached read-only mode'); + await expect(cachedTriggersAPI.acceptTrigger('Title')).rejects.toThrow('Cached read-only mode'); + }); +}); diff --git a/client/src/api/adapters/mock/index.ts b/client/src/api/adapters/mock/index.ts index 3f79d31..8b2861a 100644 --- a/client/src/api/adapters/mock/index.ts +++ b/client/src/api/adapters/mock/index.ts @@ -125,6 +125,12 @@ import type { SetHuggingFaceTokenResult, HuggingFaceTokenStatus, ValidateHuggingFaceTokenResult, + ListTasksResponse, + UpdateTaskRequest, + Task, + AnalyticsOverview, + ListSpeakerStatsResponse, + EntityAnalytics, } from '../../types'; // In-memory store @@ -2004,4 +2010,51 @@ export const mockAPI: NoteFlowAPI = { errorMessage: 'No token configured', }; }, + + async listTasks(): Promise { + await delay(100); + return { tasks: [], total_count: 0 }; + }, + + async updateTask(request: UpdateTaskRequest): Promise { + await delay(100); + return { + id: request.task_id, + meeting_id: null, + action_item_id: null, + text: request.text ?? '', + status: request.status ?? 'open', + assignee_person_id: request.assignee_person_id ?? null, + due_date: request.due_date ?? null, + priority: request.priority ?? 0, + completed_at: null, + }; + }, + + async getAnalyticsOverview(): Promise { + await delay(100); + return { + daily: [], + total_meetings: 0, + total_duration: 0, + total_words: 0, + total_segments: 0, + speaker_count: 0, + }; + }, + + async listSpeakerStats(): Promise { + await delay(100); + return { speakers: [] }; + }, + + async getEntityAnalytics(): Promise { + await delay(100); + return { + by_category: [], + top_entities: [], + total_entities: 0, + total_mentions: 0, + }; + }, }; diff --git a/client/src/api/adapters/tauri/__tests__/core-mapping.test.ts b/client/src/api/adapters/tauri/__tests__/core-mapping.test.ts index 587a64d..fe44a58 100644 --- a/client/src/api/adapters/tauri/__tests__/core-mapping.test.ts +++ b/client/src/api/adapters/tauri/__tests__/core-mapping.test.ts @@ -26,6 +26,7 @@ describe('tauri-adapter mapping (core)', () => { sort_order: 1, project_id: undefined, project_ids: [], + include_segments: false, }); }); diff --git a/client/src/api/adapters/tauri/__tests__/transcription-mapping.test.ts b/client/src/api/adapters/tauri/__tests__/transcription-mapping.test.ts index c1fdee8..119f515 100644 --- a/client/src/api/adapters/tauri/__tests__/transcription-mapping.test.ts +++ b/client/src/api/adapters/tauri/__tests__/transcription-mapping.test.ts @@ -194,9 +194,11 @@ describe('tauri-adapter mapping (transcription)', () => { expect(onError).toHaveBeenCalledWith( expect.objectContaining({ code: 'stream_close_failed', - message: expect.stringContaining('Failed to stop recording'), }) ); + const firstCall = onError.mock.calls[0]?.[0] as { message?: unknown } | undefined; + const message = typeof firstCall?.message === 'string' ? firstCall.message : String(firstCall?.message ?? ''); + expect(message).toContain('Failed to stop recording'); expect(unlisten).toHaveBeenCalledTimes(1); }); diff --git a/client/src/api/adapters/tauri/constants.ts b/client/src/api/adapters/tauri/constants.ts index a929bd1..8909867 100644 --- a/client/src/api/adapters/tauri/constants.ts +++ b/client/src/api/adapters/tauri/constants.ts @@ -148,6 +148,7 @@ export const TauriCommands = { // Analytics (Bugfinder Sprint) GET_ANALYTICS_OVERVIEW: 'get_analytics_overview', LIST_SPEAKER_STATS: 'list_speaker_stats', + GET_ENTITY_ANALYTICS: 'get_entity_analytics', } as const; /** diff --git a/client/src/api/adapters/tauri/sections/analytics.ts b/client/src/api/adapters/tauri/sections/analytics.ts index 8076c90..0a32d6d 100644 --- a/client/src/api/adapters/tauri/sections/analytics.ts +++ b/client/src/api/adapters/tauri/sections/analytics.ts @@ -1,6 +1,8 @@ import type { AnalyticsOverview, AnalyticsOverviewRequest, + EntityAnalytics, + EntityAnalyticsRequest, ListSpeakerStatsRequest, ListSpeakerStatsResponse, } from '../../../types'; @@ -10,7 +12,7 @@ import type { TauriInvoke } from '../types'; export function createAnalyticsApi( invoke: TauriInvoke -): Pick { +): Pick { return { async getAnalyticsOverview(request: AnalyticsOverviewRequest): Promise { return invoke(TauriCommands.GET_ANALYTICS_OVERVIEW, { @@ -28,5 +30,14 @@ export function createAnalyticsApi( project_ids: request.project_ids, }); }, + async getEntityAnalytics(request: EntityAnalyticsRequest): Promise { + return invoke(TauriCommands.GET_ENTITY_ANALYTICS, { + start_time: request.start_time, + end_time: request.end_time, + project_id: request.project_id, + project_ids: request.project_ids, + top_limit: request.top_limit, + }); + }, }; } diff --git a/client/src/api/adapters/tauri/sections/meetings.ts b/client/src/api/adapters/tauri/sections/meetings.ts index 65a4062..8a444bf 100644 --- a/client/src/api/adapters/tauri/sections/meetings.ts +++ b/client/src/api/adapters/tauri/sections/meetings.ts @@ -64,6 +64,7 @@ export function createMeetingApi( sort_order: sortOrderToGrpcEnum(request.sort_order), project_id: normalizeProjectId(request.project_id), project_ids: normalizeProjectIds(request.project_ids ?? []), + include_segments: request.include_segments ?? false, }); if (response.meetings?.length) { meetingCache.cacheMeetings(response.meetings); diff --git a/client/src/api/adapters/tauri/sections/sections.test.ts b/client/src/api/adapters/tauri/sections/sections.test.ts new file mode 100644 index 0000000..511545d --- /dev/null +++ b/client/src/api/adapters/tauri/sections/sections.test.ts @@ -0,0 +1,350 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { TauriCommands } from '../constants'; +import { createAnalyticsApi } from './analytics'; +import { createAppsApi } from './apps'; +import { createAsrApi } from './asr'; +import { createAudioApi } from './audio'; +import { createOidcApi } from './oidc'; +import { createProjectApi } from './projects'; +import { createTaskApi } from './tasks'; + +describe('tauri section APIs', () => { + it('maps analytics commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + + const api = createAnalyticsApi(invoke); + await api.getAnalyticsOverview({ start_time: 1, end_time: 2, project_id: 'p1' }); + await api.listSpeakerStats({ start_time: 1, end_time: 2, project_ids: ['p1'] }); + await api.getEntityAnalytics({ start_time: 1, end_time: 2, top_limit: 5 }); + + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_ANALYTICS_OVERVIEW, { + start_time: 1, + end_time: 2, + project_id: 'p1', + project_ids: undefined, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_SPEAKER_STATS, { + start_time: 1, + end_time: 2, + project_id: undefined, + project_ids: ['p1'], + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_ENTITY_ANALYTICS, { + start_time: 1, + end_time: 2, + project_id: undefined, + project_ids: undefined, + top_limit: 5, + }); + }); + + it('maps apps commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({ apps: [], total: 0, page: 0, page_size: 50, has_more: false }); + + const api = createAppsApi(invoke); + await api.listInstalledApps({ commonOnly: true, page: 1, pageSize: 10, forceRefresh: true }); + await api.invalidateAppCache(); + + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_INSTALLED_APPS, { + common_only: true, + page: 1, + page_size: 10, + force_refresh: true, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.INVALIDATE_APP_CACHE); + }); + + it('maps task commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({ tasks: [], total_count: 0 }); + invoke.mockResolvedValueOnce({ task: { id: 't1' } }); + + const api = createTaskApi(invoke); + await api.listTasks({ statuses: ['open'], limit: 10, offset: 0 }); + const updated = await api.updateTask({ task_id: 't1', text: 'hi', status: 'open' }); + + expect(updated).toEqual({ id: 't1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_TASKS, { + statuses: ['open'], + limit: 10, + offset: 0, + project_id: undefined, + project_ids: undefined, + meeting_id: undefined, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_TASK, { + task_id: 't1', + text: 'hi', + status: 'open', + assignee_person_id: undefined, + due_date: undefined, + priority: undefined, + }); + }); + + it('maps asr commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce({} as unknown); + invoke.mockResolvedValueOnce(true); + invoke.mockResolvedValueOnce({} as unknown); + + const api = createAsrApi(invoke); + await api.getAsrConfiguration(); + await api.updateAsrConfiguration({ modelSize: 'base' }); + await api.getAsrJobStatus('job1'); + await api.getStreamingConfiguration(); + await api.updateStreamingConfiguration({ partialCadenceSeconds: 2 }); + await api.setHuggingFaceToken({ token: 'hf', validate: false }); + await api.getHuggingFaceTokenStatus(); + await api.deleteHuggingFaceToken(); + await api.validateHuggingFaceToken(); + + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_ASR_CONFIGURATION); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_ASR_CONFIGURATION, { + request: { modelSize: 'base' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_ASR_JOB_STATUS, { job_id: 'job1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_STREAMING_CONFIGURATION); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_STREAMING_CONFIGURATION, { + request: { partialCadenceSeconds: 2 }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SET_HUGGINGFACE_TOKEN, { + request: { token: 'hf', validate: false }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_HUGGINGFACE_TOKEN_STATUS); + expect(invoke).toHaveBeenCalledWith(TauriCommands.DELETE_HUGGINGFACE_TOKEN); + expect(invoke).toHaveBeenCalledWith(TauriCommands.VALIDATE_HUGGINGFACE_TOKEN); + }); + + it('maps audio commands', async () => { + const invoke = vi.fn(); + invoke + .mockResolvedValueOnce([]) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce([]) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce({ enabled: true }) + .mockResolvedValueOnce({ + has_input_devices: true, + has_virtual_device: false, + input_devices: ['mic'], + is_server_connected: true, + can_run_audio_tests: true, + }) + .mockResolvedValueOnce({ + chunks_sent: 1, + duration_seconds: 2, + sample_rate: 16000, + }) + .mockResolvedValueOnce({ + chunks_sent: 3, + duration_seconds: 4, + sample_rate: 8000, + }); + + const api = createAudioApi(invoke); + await api.listAudioDevices(); + await api.getDefaultAudioDevice(true); + await api.selectAudioDevice('mic', true); + await api.listLoopbackDevices(); + await api.setSystemAudioDevice('loopback'); + await api.setDualCaptureEnabled(true); + await api.setAudioMixLevels(0.5, 0.25); + await api.getDualCaptureConfig(); + const env = await api.checkTestEnvironment(); + const audio = await api.injectTestAudio('m1', { wavPath: '/tmp.wav', speed: 1.5, chunkMs: 250 }); + const tone = await api.injectTestTone('m1', 440, 2, 16000); + + expect(env.canRunAudioTests).toBe(true); + expect(audio.sampleRate).toBe(16000); + expect(tone.chunksSent).toBe(3); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_AUDIO_DEVICES); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_DEFAULT_AUDIO_DEVICE, { is_input: true }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SELECT_AUDIO_DEVICE, { + device_id: 'mic', + is_input: true, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_LOOPBACK_DEVICES); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SET_SYSTEM_AUDIO_DEVICE, { + device_id: 'loopback', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SET_DUAL_CAPTURE_ENABLED, { enabled: true }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SET_AUDIO_MIX_LEVELS, { + mic_gain: 0.5, + system_gain: 0.25, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_DUAL_CAPTURE_CONFIG); + expect(invoke).toHaveBeenCalledWith(TauriCommands.CHECK_TEST_ENVIRONMENT); + expect(invoke).toHaveBeenCalledWith(TauriCommands.INJECT_TEST_AUDIO, { + meeting_id: 'm1', + config: { wav_path: '/tmp.wav', speed: 1.5, chunk_ms: 250 }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.INJECT_TEST_TONE, { + meeting_id: 'm1', + frequency_hz: 440, + duration_seconds: 2, + sample_rate: 16000, + }); + }); + + it('maps oidc commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({ id: 'oidc' }); + invoke.mockResolvedValueOnce({ providers: [] }); + invoke.mockResolvedValueOnce({ id: 'oidc' }); + invoke.mockResolvedValueOnce({ id: 'oidc' }); + invoke.mockResolvedValueOnce({ success: true }); + invoke.mockResolvedValueOnce({ success: true }); + invoke.mockResolvedValueOnce({ success: true }); + invoke.mockResolvedValueOnce({ presets: [] }); + + const api = createOidcApi(invoke); + await api.registerOidcProvider({ + workspace_id: 'w1', + name: 'oidc', + issuer_url: 'https://issuer', + client_id: 'client', + preset: 'custom', + scopes: ['openid'], + allowed_groups: [], + auto_discover: true, + }); + await api.listOidcProviders('w1', true); + await api.getOidcProvider('oidc'); + await api.updateOidcProvider({ + provider_id: 'oidc', + name: 'oidc2', + scopes: ['openid'], + allowed_groups: [], + }); + await api.deleteOidcProvider('oidc'); + await api.refreshOidcDiscovery('oidc', 'w1'); + await api.testOidcConnection('oidc'); + await api.listOidcPresets(); + + expect(invoke).toHaveBeenCalledWith(TauriCommands.REGISTER_OIDC_PROVIDER, { + request: { + workspace_id: 'w1', + name: 'oidc', + issuer_url: 'https://issuer', + client_id: 'client', + preset: 'custom', + scopes: ['openid'], + allowed_groups: [], + auto_discover: true, + }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_OIDC_PROVIDERS, { + workspace_id: 'w1', + enabled_only: true, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_OIDC_PROVIDER, { provider_id: 'oidc' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_OIDC_PROVIDER, { + request: { + provider_id: 'oidc', + name: 'oidc2', + scopes: ['openid'], + allowed_groups: [], + }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.DELETE_OIDC_PROVIDER, { + provider_id: 'oidc', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.REFRESH_OIDC_DISCOVERY, { + provider_id: 'oidc', + workspace_id: 'w1', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.TEST_OIDC_CONNECTION, { + provider_id: 'oidc', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_OIDC_PRESETS); + }); + + it('maps project commands', async () => { + const invoke = vi.fn(); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ projects: [], total_count: 0 }); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ id: 'p1' }); + invoke.mockResolvedValueOnce({ success: true }); + invoke.mockResolvedValueOnce(undefined); + invoke.mockResolvedValueOnce({ project_id: 'p1', project: { id: 'p1' } }); + invoke.mockResolvedValueOnce({ id: 'member' }); + invoke.mockResolvedValueOnce({ id: 'member' }); + invoke.mockResolvedValueOnce({ success: true }); + invoke.mockResolvedValueOnce({ members: [], total_count: 0 }); + + const api = createProjectApi(invoke); + await api.createProject({ workspace_id: 'w1', name: 'Project' }); + await api.getProject({ project_id: 'p1' }); + await api.getProjectBySlug({ workspace_id: 'w1', slug: 'proj' }); + await api.listProjects({ workspace_id: 'w1' }); + await api.updateProject({ project_id: 'p1', name: 'Project 2' }); + await api.archiveProject('p1'); + await api.restoreProject('p1'); + await api.deleteProject('p1'); + await api.setActiveProject({ workspace_id: 'w1', project_id: 'p1' }); + await api.getActiveProject({ workspace_id: 'w1' }); + await api.addProjectMember({ project_id: 'p1', user_id: 'u1', role: 'viewer' }); + await api.updateProjectMemberRole({ project_id: 'p1', user_id: 'u1', role: 'editor' }); + await api.removeProjectMember({ project_id: 'p1', user_id: 'u1' }); + await api.listProjectMembers({ project_id: 'p1', limit: 10, offset: 0 }); + + expect(invoke).toHaveBeenCalledWith(TauriCommands.CREATE_PROJECT, { + request: { workspace_id: 'w1', name: 'Project' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_PROJECT, { project_id: 'p1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_PROJECT_BY_SLUG, { + workspace_id: 'w1', + slug: 'proj', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_PROJECTS, { + workspace_id: 'w1', + include_archived: false, + limit: undefined, + offset: undefined, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_PROJECT, { + request: { project_id: 'p1', name: 'Project 2' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.ARCHIVE_PROJECT, { project_id: 'p1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.RESTORE_PROJECT, { project_id: 'p1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.DELETE_PROJECT, { project_id: 'p1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.SET_ACTIVE_PROJECT, { + workspace_id: 'w1', + project_id: 'p1', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_ACTIVE_PROJECT, { workspace_id: 'w1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.ADD_PROJECT_MEMBER, { + request: { project_id: 'p1', user_id: 'u1', role: 'viewer' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_PROJECT_MEMBER_ROLE, { + request: { project_id: 'p1', user_id: 'u1', role: 'editor' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.REMOVE_PROJECT_MEMBER, { + request: { project_id: 'p1', user_id: 'u1' }, + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_PROJECT_MEMBERS, { + project_id: 'p1', + limit: 10, + offset: 0, + }); + }); +}); diff --git a/client/src/api/adapters/tauri/sections/summarization.test.ts b/client/src/api/adapters/tauri/sections/summarization.test.ts new file mode 100644 index 0000000..8a4a232 --- /dev/null +++ b/client/src/api/adapters/tauri/sections/summarization.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from 'vitest'; +import { createSummarizationApi } from './summarization'; +import { TauriCommands } from '../constants'; +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +describe('createSummarizationApi', () => { + it('maps summarization template operations', async () => { + const invoke = vi.fn().mockResolvedValue({}); + const api = createSummarizationApi(invoke); + + await api.listSummarizationTemplates({ workspace_id: 'w1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_SUMMARIZATION_TEMPLATES, { + workspace_id: 'w1', + include_system: true, + include_archived: false, + limit: undefined, + offset: undefined, + }); + + await api.getSummarizationTemplate({ template_id: 't1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GET_SUMMARIZATION_TEMPLATE, { + template_id: 't1', + include_current_version: true, + }); + + await api.createSummarizationTemplate({ + workspace_id: 'w1', + name: 'Name', + description: 'Desc', + content: 'Content', + change_note: 'Note', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.CREATE_SUMMARIZATION_TEMPLATE, { + workspace_id: 'w1', + name: 'Name', + description: 'Desc', + content: 'Content', + change_note: 'Note', + }); + + await api.updateSummarizationTemplate({ + template_id: 't1', + name: 'New', + description: 'Desc', + content: 'Content', + change_note: 'Note', + }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.UPDATE_SUMMARIZATION_TEMPLATE, { + template_id: 't1', + name: 'New', + description: 'Desc', + content: 'Content', + change_note: 'Note', + }); + + await api.archiveSummarizationTemplate({ template_id: 't1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.ARCHIVE_SUMMARIZATION_TEMPLATE, { + template_id: 't1', + }); + + await api.listSummarizationTemplateVersions({ template_id: 't1', limit: 5, offset: 0 }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.LIST_SUMMARIZATION_TEMPLATE_VERSIONS, { + template_id: 't1', + limit: 5, + offset: 0, + }); + + await api.restoreSummarizationTemplateVersion({ template_id: 't1', version_id: 'v1' }); + expect(invoke).toHaveBeenCalledWith(TauriCommands.RESTORE_SUMMARIZATION_TEMPLATE_VERSION, { + template_id: 't1', + version_id: 'v1', + }); + }); + + it('tracks cloud consent actions and status', async () => { + const invoke = vi.fn().mockResolvedValue({ consent_granted: true }); + const api = createSummarizationApi(invoke); + + await api.grantCloudConsent(); + expect(invoke).toHaveBeenCalledWith(TauriCommands.GRANT_CLOUD_CONSENT); + + await api.revokeCloudConsent(); + expect(invoke).toHaveBeenCalledWith(TauriCommands.REVOKE_CLOUD_CONSENT); + + const status = await api.getCloudConsentStatus(); + expect(status.consentGranted).toBe(true); + }); +}); diff --git a/client/src/api/adapters/tauri/utils.test.ts b/client/src/api/adapters/tauri/utils.test.ts new file mode 100644 index 0000000..17f51b7 --- /dev/null +++ b/client/src/api/adapters/tauri/utils.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; + +import { IdentityDefaults } from '@/api/core/constants'; +import { normalizeProjectId, normalizeProjectIds, recordingBlockedDetails, RECORDING_BLOCKED_PREFIX } from '@/api/adapters/tauri/utils'; + +describe('tauri utils', () => { + it('normalizes project ids', () => { + expect(normalizeProjectId()).toBeUndefined(); + expect(normalizeProjectId(' ')).toBeUndefined(); + expect(normalizeProjectId(IdentityDefaults.DEFAULT_PROJECT_ID)).toBeUndefined(); + expect(normalizeProjectId(' custom ')).toBe('custom'); + + const ids = normalizeProjectIds([ + ' one ', + IdentityDefaults.DEFAULT_PROJECT_ID, + '', + 'two', + ]); + expect(ids).toEqual(['one', 'two']); + }); + + it('extracts recording blocked details', () => { + const message = `${RECORDING_BLOCKED_PREFIX}: rule_id=abc, rule_label=Focus, app_name=Zoom`; + const details = recordingBlockedDetails(message); + expect(details).toEqual({ ruleId: 'abc', ruleLabel: 'Focus', appName: 'Zoom' }); + + const errorDetails = recordingBlockedDetails(new Error(message)); + expect(errorDetails?.ruleId).toBe('abc'); + + expect(recordingBlockedDetails('nope')).toBeNull(); + }); +}); diff --git a/client/src/api/interface.ts b/client/src/api/interface.ts index 402d44e..2ac3bc9 100644 --- a/client/src/api/interface.ts +++ b/client/src/api/interface.ts @@ -16,6 +16,8 @@ import type { AddProjectMemberRequest, AnalyticsOverview, AnalyticsOverviewRequest, + EntityAnalytics, + EntityAnalyticsRequest, ArchiveSummarizationTemplateRequest, Annotation, ASRConfiguration, @@ -958,6 +960,8 @@ export interface NoteFlowAPI { getAnalyticsOverview(request: AnalyticsOverviewRequest): Promise; listSpeakerStats(request: ListSpeakerStatsRequest): Promise; + + getEntityAnalytics(request: EntityAnalyticsRequest): Promise; } // --- API Instance Management --- diff --git a/client/src/api/types/features/analytics.ts b/client/src/api/types/features/analytics.ts index d4f2d00..937ee61 100644 --- a/client/src/api/types/features/analytics.ts +++ b/client/src/api/types/features/analytics.ts @@ -47,3 +47,31 @@ export interface ListSpeakerStatsRequest { export interface ListSpeakerStatsResponse { speakers: SpeakerStat[]; } + +export interface EntityCategoryStat { + category: string; + count: number; + total_mentions: number; +} + +export interface TopEntity { + text: string; + category: string; + mention_count: number; + meeting_count: number; +} + +export interface EntityAnalytics { + by_category: EntityCategoryStat[]; + top_entities: TopEntity[]; + total_entities: number; + total_mentions: number; +} + +export interface EntityAnalyticsRequest { + start_time: number; + end_time: number; + project_id?: string; + project_ids?: string[]; + top_limit?: number; +} diff --git a/client/src/api/types/features/sync.ts b/client/src/api/types/features/sync.ts index c2ee6c6..21da4f1 100644 --- a/client/src/api/types/features/sync.ts +++ b/client/src/api/types/features/sync.ts @@ -47,8 +47,6 @@ export function getSyncErrorMessage(errorCode: SyncErrorCode | undefined): strin return 'External service error - please try again later'; case 'internal_error': return 'Internal error - please contact support'; - case 'unknown': - case 'unspecified': default: return 'Sync failed'; } diff --git a/client/src/api/types/requests/meetings.ts b/client/src/api/types/requests/meetings.ts index fc07890..edee68e 100644 --- a/client/src/api/types/requests/meetings.ts +++ b/client/src/api/types/requests/meetings.ts @@ -31,6 +31,8 @@ export interface ListMeetingsRequest { project_id?: string; /** Optional project scope for multiple projects (overrides project_id when provided) */ project_ids?: string[]; + /** Include full transcript segments (default: false) */ + include_segments?: boolean; } /** diff --git a/client/src/api/types/requests/preferences.ts b/client/src/api/types/requests/preferences.ts index 2be9d43..66e6882 100644 --- a/client/src/api/types/requests/preferences.ts +++ b/client/src/api/types/requests/preferences.ts @@ -55,4 +55,5 @@ export interface UserPreferences { meetings_project_ids: string[]; tasks_project_scope: ProjectScope; tasks_project_ids: string[]; + tasks_view_mode: 'list' | 'board'; } diff --git a/client/src/components/common/badges/annotation-type-badge.test.tsx b/client/src/components/common/badges/annotation-type-badge.test.tsx new file mode 100644 index 0000000..19b62b0 --- /dev/null +++ b/client/src/components/common/badges/annotation-type-badge.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { AnnotationTypeBadge } from '@/components/common/badges/annotation-type-badge'; + +describe('AnnotationTypeBadge', () => { + it('renders label and icon by default', () => { + const { container } = render(); + expect(screen.getByText('Action')).toBeInTheDocument(); + expect(container.querySelector('svg')).not.toBeNull(); + }); + + it('renders without icon when disabled', () => { + render(); + expect(screen.getByText('Note')).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/common/dialogs/confirmation-dialog.test.tsx b/client/src/components/common/dialogs/confirmation-dialog.test.tsx new file mode 100644 index 0000000..3ae93f6 --- /dev/null +++ b/client/src/components/common/dialogs/confirmation-dialog.test.tsx @@ -0,0 +1,55 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { ConfirmationDialog } from '@/components/common/dialogs/confirmation-dialog'; + +const TITLE = 'Confirm Action'; +const DESCRIPTION = 'Are you sure?'; +const CONFIRM = 'Confirm'; + +function renderDialog(props?: Partial[0]>) { + const onOpenChange = vi.fn(); + const onConfirm = vi.fn(); + const onCancel = vi.fn(); + + render( + + ); + + return { onOpenChange, onConfirm, onCancel }; +} + +describe('ConfirmationDialog', () => { + it('renders content and handles confirm', async () => { + const { onOpenChange, onConfirm } = renderDialog(); + + expect(screen.getByText(TITLE)).toBeInTheDocument(); + expect(screen.getByText(DESCRIPTION)).toBeInTheDocument(); + + fireEvent.click(screen.getByRole('button', { name: CONFIRM })); + + expect(onConfirm).toHaveBeenCalled(); + expect(onOpenChange).toHaveBeenCalledWith(false); + }); + + it('handles cancel and hides cancel button when disabled', async () => { + const { onOpenChange, onCancel } = renderDialog({ cancelContent: 'Nope' }); + + fireEvent.click(screen.getByRole('button', { name: 'Nope' })); + + expect(onCancel).toHaveBeenCalled(); + expect(onOpenChange).toHaveBeenCalledWith(false); + + renderDialog({ showCancel: false, cancelContent: 'Nope' }); + expect(screen.queryByRole('button', { name: 'Nope' })).toBeNull(); + }); +}); diff --git a/client/src/components/common/error-boundary.test.tsx b/client/src/components/common/error-boundary.test.tsx new file mode 100644 index 0000000..bc1739d --- /dev/null +++ b/client/src/components/common/error-boundary.test.tsx @@ -0,0 +1,45 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { ErrorBoundary } from '@/components/common/error-boundary'; + +function Thrower() { + throw new Error('Kaboom'); +} + +describe('ErrorBoundary', () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('renders children when no error', () => { + render( + +
All good
+
+ ); + + expect(screen.getByText('All good')).toBeInTheDocument(); + }); + + it('renders fallback UI on error and reloads', async () => { + const reloadSpy = vi.fn(); + vi.stubGlobal('location', { ...window.location, reload: reloadSpy }); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); + + render( + + + + ); + + expect(screen.getByText('Something went wrong')).toBeInTheDocument(); + expect(screen.getByText('Kaboom')).toBeInTheDocument(); + + fireEvent.click(screen.getByRole('button', { name: /reload app/i })); + expect(reloadSpy).toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); +}); diff --git a/client/src/components/common/nav-link.test.tsx b/client/src/components/common/nav-link.test.tsx new file mode 100644 index 0000000..aa2495b --- /dev/null +++ b/client/src/components/common/nav-link.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { MemoryRouter } from 'react-router-dom'; +import { NavLink } from './nav-link'; + +describe('NavLink', () => { + it('applies active class when route matches', () => { + render( + + + Active + + + ); + + expect(screen.getByRole('link', { name: 'Active' })).toHaveClass('is-active'); + }); + + it('does not apply active class when route does not match', () => { + render( + + + Active + + + ); + + expect(screen.getByRole('link', { name: 'Active' })).not.toHaveClass('is-active'); + }); +}); diff --git a/client/src/components/common/stats-card.test.tsx b/client/src/components/common/stats-card.test.tsx new file mode 100644 index 0000000..b86d883 --- /dev/null +++ b/client/src/components/common/stats-card.test.tsx @@ -0,0 +1,40 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { CenteredStatsCard, MiniStatsCard, StatsCard } from './stats-card'; + +const Icon = ({ className }: { className?: string }) => ( + +); + +describe('StatsCard', () => { + it('renders title, value, description, and variant styles', () => { + render( + + ); + + expect(screen.getByText('Sessions')).toBeInTheDocument(); + expect(screen.getByText('12')).toHaveClass('text-success'); + expect(screen.getByText('Monthly')).toBeInTheDocument(); + }); + + it('renders mini stats card with custom icon background', () => { + render(); + + expect(screen.getByText('5')).toBeInTheDocument(); + expect(screen.getByText('Errors')).toBeInTheDocument(); + expect(screen.getByTestId('icon').parentElement).toHaveClass('bg-red-500'); + }); + + it('renders centered stats card', () => { + render(); + + expect(screen.getByText('42')).toHaveClass('text-destructive'); + expect(screen.getByText('Total')).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/dev/dev-profiler.tsx b/client/src/components/dev/dev-profiler.tsx index 3c1a90f..5af5562 100644 --- a/client/src/components/dev/dev-profiler.tsx +++ b/client/src/components/dev/dev-profiler.tsx @@ -24,7 +24,7 @@ const profileSamples: ProfileSample[] = []; const profileListeners = new Set(); function isDevMode(): boolean { - return typeof import.meta !== 'undefined' && import.meta.env.DEV; + return Boolean(import.meta?.env?.DEV); } function readProfilerEnabled(): boolean { diff --git a/client/src/components/features/analytics/analytics-utils.test.ts b/client/src/components/features/analytics/analytics-utils.test.ts new file mode 100644 index 0000000..81c6ef2 --- /dev/null +++ b/client/src/components/features/analytics/analytics-utils.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; + +import { mapSpeakerStats, speakerLabel, wordCountTickLabel } from '@/components/features/analytics/analytics-utils'; + +describe('analytics-utils', () => { + it('formats speaker labels safely', () => { + expect(speakerLabel(null)).toBe(''); + expect(speakerLabel({})).toBe(''); + expect(speakerLabel({ speakerId: 'spk', percentage: 12.345 })).toBe('spk: 12.3%'); + }); + + it('formats word count ticks', () => { + expect(wordCountTickLabel('abc')).toBe(''); + expect(wordCountTickLabel(999)).toBe('999'); + expect(wordCountTickLabel(1200)).toBe('1.2k'); + }); + + it('maps speaker stats with percentages', () => { + const mapped = mapSpeakerStats([ + { + speaker_id: 'a', + display_name: 'A', + total_time: 50, + segment_count: 2, + meeting_count: 1, + }, + { + speaker_id: 'b', + display_name: 'B', + total_time: 50, + segment_count: 1, + meeting_count: 1, + }, + ]); + expect(mapped[0]?.percentage).toBe(50); + expect(mapped[1]?.percentage).toBe(50); + }); +}); diff --git a/client/src/components/features/analytics/analytics-utils.ts b/client/src/components/features/analytics/analytics-utils.ts index 9aa03dd..69c2f3c 100644 --- a/client/src/components/features/analytics/analytics-utils.ts +++ b/client/src/components/features/analytics/analytics-utils.ts @@ -1,3 +1,5 @@ +import type { SpeakerStat } from '@/api/types'; + export const SPEAKER_COLORS = [ 'hsl(var(--chart-1))', 'hsl(var(--chart-2))', @@ -14,6 +16,15 @@ export const SPEAKER_COLOR_CLASSES = [ 'bg-[hsl(var(--chart-5))]', ]; +export interface SpeakerStats { + speakerId: string; + displayName: string; + totalTime: number; + percentage: number; + segmentCount: number; + meetingCount: number; +} + export function speakerLabel(entry: unknown): string { if (!entry || typeof entry !== 'object') { return ''; @@ -34,3 +45,15 @@ export function wordCountTickLabel(value: unknown): string { } return numeric >= 1000 ? `${(numeric / 1000).toFixed(1)}k` : `${numeric}`; } + +export function mapSpeakerStats(speakers: SpeakerStat[]): SpeakerStats[] { + const totalTime = speakers.reduce((sum, s) => sum + s.total_time, 0); + return speakers.map((s) => ({ + speakerId: s.speaker_id, + displayName: s.display_name, + totalTime: s.total_time, + percentage: totalTime > 0 ? (s.total_time / totalTime) * 100 : 0, + segmentCount: s.segment_count, + meetingCount: s.meeting_count, + })); +} diff --git a/client/src/components/features/analytics/entities-tab.tsx b/client/src/components/features/analytics/entities-tab.tsx new file mode 100644 index 0000000..6fde4fb --- /dev/null +++ b/client/src/components/features/analytics/entities-tab.tsx @@ -0,0 +1,216 @@ +import { Bar, BarChart, CartesianGrid, Cell, Legend, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +import { FileText, Hash, Tag, TrendingUp } from 'lucide-react'; +import type { EntityAnalytics } from '@/api/types'; +import { StatsCard } from '@/components/common'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; +import { chartAxis, chartStrokes, flexLayout, overflow } from '@/lib/ui/styles'; + +const titleRowClass = flexLayout.itemsGap2; + +const CATEGORY_COLORS = [ + 'hsl(var(--chart-1))', + 'hsl(var(--chart-2))', + 'hsl(var(--chart-3))', + 'hsl(var(--chart-4))', + 'hsl(var(--chart-5))', + 'hsl(221, 83%, 53%)', + 'hsl(262, 83%, 58%)', + 'hsl(330, 81%, 60%)', +]; + +interface EntitiesTabProps { + entityAnalytics: EntityAnalytics; +} + +export function EntitiesTab({ entityAnalytics }: EntitiesTabProps) { + const categoryData = entityAnalytics.by_category.map((cat, idx) => ({ + category: cat.category, + entity_count: cat.count, + mention_count: cat.total_mentions, + fill: CATEGORY_COLORS[idx % CATEGORY_COLORS.length], + })); + + const categoryLabel = (label: unknown): string => { + if (typeof label !== 'string') { + return String(label ?? ''); + } + if (!label) { + return ''; + } + return label.charAt(0).toUpperCase() + label.slice(1); + }; + + const chartConfig = entityAnalytics.by_category.reduce< + Record + >((acc, cat, idx) => { + acc[cat.category] = { + label: categoryLabel(cat.category), + color: CATEGORY_COLORS[idx % CATEGORY_COLORS.length], + }; + return acc; + }, {}); + + return ( +
+
+ + + + 0 + ? (entityAnalytics.total_mentions / entityAnalytics.total_entities).toFixed(1) + : '0' + } + description="Mentions per entity" + /> +
+ +
+ + + + + Entities by Category + + Distribution of entity types + + +
+ {categoryData.length > 0 ? ( + + + + `${category} (${(percent * 100).toFixed(0)}%)` + } + labelLine={false} + > + {categoryData.map((entry) => ( + + ))} + + [ + `${Number(value)} entities`, + categoryLabel(name), + ]} + /> + + + + ) : ( +

No entity data available

+ )} +
+
+
+ + + + + + Mentions by Category + + How often each category is mentioned + + +
+ + + + + categoryLabel(v)} + /> + `${Number(value).toLocaleString()} mentions`} + /> + } + /> + + {categoryData.map((entry) => ( + + ))} + + + +
+
+
+
+ + {entityAnalytics.top_entities.length > 0 && ( + + + + + Top Entities + + Most frequently mentioned entities across your meetings + + +
+ + + + + + + + + + + {entityAnalytics.top_entities.map((entity) => ( + + + + + + + ))} + +
EntityCategoryMentionsMeetings
{entity.text} + + {entity.category.charAt(0).toUpperCase() + entity.category.slice(1)} + + {entity.mention_count.toLocaleString()}{entity.meeting_count.toLocaleString()}
+
+
+
+ )} +
+ ); +} diff --git a/client/src/components/features/analytics/log-timeline.test.tsx b/client/src/components/features/analytics/log-timeline.test.tsx new file mode 100644 index 0000000..0e23f5e --- /dev/null +++ b/client/src/components/features/analytics/log-timeline.test.tsx @@ -0,0 +1,114 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { LogTimeline } from '@/components/features/analytics/log-timeline'; +import { summarizeLogGroup } from '@/lib/observability/group-summarizer'; +import type { LogGroup } from '@/lib/observability/groups'; +import type { LogEntryData } from '@/components/features/analytics/log-entry'; +import type { VirtualItem } from '@tanstack/react-virtual'; + +function createLog(overrides: Partial): LogEntryData { + return { + id: overrides.id ?? 'log-1', + timestamp: overrides.timestamp ?? Date.now(), + level: overrides.level ?? 'info', + source: overrides.source ?? 'app', + message: overrides.message ?? 'Hello', + origin: overrides.origin ?? 'client', + ...overrides, + }; +} + +function createGroup(group: Partial & { id: string; logs: LogEntryData[] }): LogGroup { + const summary = group.summary ?? summarizeLogGroup(group.logs); + const timestamps = group.logs.map((log) => log.timestamp); + const startTime = group.startTime ?? Math.min(...timestamps); + const endTime = group.endTime ?? Math.max(...timestamps); + + return { + id: group.id, + groupType: group.groupType ?? 'meeting', + label: group.label ?? 'Group', + logs: group.logs, + summary, + startTime, + endTime, + entityId: group.entityId, + operationId: group.operationId, + }; +} + +describe('LogTimeline', () => { + it('returns null when no groups', () => { + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + }); + + it('renders groups with badges, gaps, and hidden count', () => { + const now = Date.now(); + const errorLog = createLog({ id: 'err', level: 'error', timestamp: now }); + const infoLog = createLog({ id: 'info', level: 'info', timestamp: now - 1 }); + const warningLog = createLog({ id: 'warn', level: 'warning', timestamp: now - 200000 }); + + const groups: LogGroup[] = [ + createGroup({ id: 'g1', label: 'Errors', logs: [errorLog, infoLog], groupType: 'time' }), + createGroup({ id: 'g2', label: 'Warnings', logs: [warningLog], groupType: 'operation' }), + ]; + + render( + + ); + + expect(screen.getByText('Errors')).toBeInTheDocument(); + expect(screen.getByText('Warnings')).toBeInTheDocument(); + + expect(screen.getAllByText('1 error').length).toBeGreaterThan(0); + expect(screen.getAllByText('1 warning').length).toBeGreaterThan(0); + + expect(screen.getAllByText('2 logs').length).toBeGreaterThan(0); + + expect(screen.getByText(/minute/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /more log/i })).toBeInTheDocument(); + }); + + it('renders virtualized groups when provided', () => { + const log = createLog({ id: 'v1', level: 'info' }); + const groups = [createGroup({ id: 'vg', label: 'Virtual', logs: [log] })]; + const virtualItem: VirtualItem = { + key: '0', + index: 0, + start: 0, + end: 10, + size: 10, + lane: 0, + }; + + render( + + ); + + expect(screen.getByText('Virtual')).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/features/analytics/logs-tab-list.test.tsx b/client/src/components/features/analytics/logs-tab-list.test.tsx new file mode 100644 index 0000000..a454f75 --- /dev/null +++ b/client/src/components/features/analytics/logs-tab-list.test.tsx @@ -0,0 +1,131 @@ +import { createRef } from 'react'; +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { LogsTabList } from '@/components/features/analytics/logs-tab-list'; +import type { LogEntryData } from '@/components/features/analytics/log-entry'; +import type { LogGroup } from '@/lib/observability/groups'; +import { summarizeLogGroup } from '@/lib/observability/group-summarizer'; +import type { SummarizedLog } from '@/lib/observability/summarizer'; + +vi.mock('@/components/features/analytics/log-entry', () => ({ + LogEntry: ({ summarized }: { summarized: { log: { id: string } } }) => ( +
{summarized.log.id}
+ ), +})); + +vi.mock('@/components/features/analytics/log-timeline', () => ({ + LogTimeline: () =>
, +})); + +function baseProps() { + return { + isLoading: false, + filteredLogs: [], + summarizedLogs: [], + logGroups: [], + groupMode: 'none' as const, + viewMode: 'friendly' as const, + expandedLogs: new Set(), + onToggleExpanded: vi.fn(), + searchQuery: '', + levelFilter: 'all' as const, + sourceFilter: 'all' as const, + originFilter: 'all' as const, + shouldVirtualizeLogs: false, + shouldVirtualizeGroups: false, + viewportRef: createRef(), + virtualItems: [], + virtualTotalSize: 0, + measureElement: vi.fn(), + groupVirtualItems: [], + groupVirtualTotalSize: 0, + groupMeasureElement: vi.fn(), + }; +} + +const buildLog = (id: string): LogEntryData => ({ + id, + timestamp: 123, + level: 'info', + source: 'app', + message: 'Hello', + origin: 'client', +}); + +const buildGroup = (log: LogEntryData): LogGroup => { + const summary = summarizeLogGroup([log]); + return { + id: 'group-1', + groupType: 'meeting', + label: 'Group', + logs: [log], + summary, + startTime: log.timestamp, + endTime: log.timestamp, + }; +}; + +describe('LogsTabList', () => { + it('renders loading state', () => { + render(); + expect(screen.getByText('Loading logs...')).toBeInTheDocument(); + }); + + it('renders empty state with and without filters', () => { + render(); + expect(screen.getByText('Logs will appear here as events occur')).toBeInTheDocument(); + + render( + + ); + expect(screen.getByText('Try adjusting your filters')).toBeInTheDocument(); + }); + + it('renders grouped logs via timeline', () => { + const log = buildLog('l1'); + render( + + ); + expect(screen.getByTestId('log-timeline')).toBeInTheDocument(); + }); + + it('renders virtualized and non-virtualized log lists', () => { + const log = buildLog('l1'); + const summarizedLogs: SummarizedLog[] = [ + { log, count: 1, isGroup: false, groupedLogs: undefined }, + ]; + + const { unmount } = render( + + ); + expect(screen.getByTestId('log-entry')).toHaveTextContent('l1'); + + unmount(); + + render( + + ); + expect(screen.getAllByTestId('log-entry')).toHaveLength(1); + }); +}); diff --git a/client/src/components/features/analytics/meetings-tab.tsx b/client/src/components/features/analytics/meetings-tab.tsx new file mode 100644 index 0000000..439a3b2 --- /dev/null +++ b/client/src/components/features/analytics/meetings-tab.tsx @@ -0,0 +1,324 @@ +// Meetings analytics tab content + +import { useId } from 'react'; +import { format } from 'date-fns'; +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + Cell, + Legend, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { Calendar, Clock, FileText, Mic, TrendingUp, Users } from 'lucide-react'; +import type { AnalyticsOverview } from '@/api/types'; +import { + SPEAKER_COLORS, + SPEAKER_COLOR_CLASSES, + type SpeakerStats, + speakerLabel, + wordCountTickLabel, +} from './analytics-utils'; +import { StatsCard } from '@/components/common'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; +import { formatDuration } from '@/lib/utils/format'; +import { chartAxis, chartHeight, chartStrokes, flexLayout, overflow, typography } from '@/lib/ui/styles'; +import { cn } from '@/lib/utils'; + +const titleRowClass = flexLayout.itemsGap2; +const ANALYTICS_DAYS = 14; + +interface DailyStats { + date: string; + dateLabel: string; + meetings: number; + totalDuration: number; + wordCount: number; +} + +function mapDailyStats(overview: AnalyticsOverview): DailyStats[] { + return overview.daily.map((d) => ({ + date: d.date, + dateLabel: format(new Date(d.date), 'MMM d'), + meetings: d.meetings, + totalDuration: d.total_duration, + wordCount: d.word_count, + })); +} + +interface MeetingsTabProps { + overview: AnalyticsOverview; + speakerStats: SpeakerStats[]; + chartConfig: Record; +} + +export function MeetingsTab({ overview, speakerStats, chartConfig }: MeetingsTabProps) { + const chartId = useId(); + const gridProps = { strokeDasharray: '3 3', className: chartStrokes.muted }; + const durationTooltip = ( + formatDuration(Number(value))} /> + ); + const defaultTooltip = ; + const wordsTooltip = ( + `${Number(value).toLocaleString()} words`} /> + ); + + const dailyTrends = mapDailyStats(overview); + const avgDuration = + overview.total_meetings > 0 ? overview.total_duration / overview.total_meetings : 0; + const avgWordsPerMeeting = + overview.total_meetings > 0 ? overview.total_words / overview.total_meetings : 0; + + return ( +
+
+ + + + +
+ +
+ + + + + Meeting Duration Trends + + Daily meeting duration over the last {ANALYTICS_DAYS} days + + +
+ + + + + + + + + + + `${Math.round(v / 60)}m`} + /> + + + + +
+
+
+ + + + + + Meetings Per Day + + Number of meetings recorded each day + + +
+ + + + + + + + + +
+
+
+
+ +
+ + + + + Speaker Participation + + Speaking time distribution across all meetings + + +
+ {speakerStats.length > 0 ? ( + + + + {speakerStats.map((stat, idx) => ( + + ))} + + [`${value.toFixed(1)}%`, name]} + /> + + + + ) : ( +

No speaker data available

+ )} +
+
+
+ + + + + + Word Count Trends + + Words transcribed per day + + +
+ + + + + + + + + + + + + + + +
+
+
+
+ + {speakerStats.length > 0 && ( + + + Speaker Breakdown + Detailed speaking time by speaker + + +
+ {speakerStats.map((speaker, index) => { + const speakerColorClass = + SPEAKER_COLOR_CLASSES[index % SPEAKER_COLOR_CLASSES.length]; + const barWidth = `${speaker.percentage}%`; + + return ( +
+
+
+
+ + {speaker.displayName} + + + {formatDuration(speaker.totalTime)} ({speaker.percentage.toFixed(1)}%) + +
+
+
+
+
+
+ ); + })} +
+ + + )} +
+ ); +} diff --git a/client/src/components/features/calendar/calendar-connection-panel.test.tsx b/client/src/components/features/calendar/calendar-connection-panel.test.tsx new file mode 100644 index 0000000..c3f8629 --- /dev/null +++ b/client/src/components/features/calendar/calendar-connection-panel.test.tsx @@ -0,0 +1,86 @@ +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { CalendarConnectionPanel } from '@/components/features/calendar/calendar-connection-panel'; + +const useOAuthFlow = vi.fn(); + +vi.mock('@/hooks', () => ({ + useOAuthFlow: () => useOAuthFlow(), +})); + +describe('CalendarConnectionPanel', () => { + it('shows empty state and error alert', () => { + useOAuthFlow.mockReturnValue({ + state: { status: 'error', error: 'Boom' }, + initiateAuth: vi.fn(), + checkConnection: vi.fn(), + disconnect: vi.fn(), + reset: vi.fn(), + }); + + render(); + + expect(screen.getByText('Boom')).toBeInTheDocument(); + expect( + screen.getByText('No calendar providers available. Check your server configuration.') + ).toBeInTheDocument(); + }); + + it('handles connect and disconnect actions', async () => { + const initiateAuth = vi.fn(); + const checkConnection = vi.fn(); + const disconnect = vi.fn().mockResolvedValue(true); + const onConnectionChange = vi.fn(); + + useOAuthFlow.mockReturnValue({ + state: { status: 'idle', provider: null, connection: null }, + initiateAuth, + checkConnection, + disconnect, + reset: vi.fn(), + }); + + render( + + ); + + expect(checkConnection).toHaveBeenCalledWith('google'); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /disconnect/i })); + }); + await waitFor(() => { + expect(onConnectionChange).toHaveBeenCalled(); + }); + + fireEvent.click(screen.getByRole('button', { name: /^connect$/i })); + expect(initiateAuth).toHaveBeenCalledWith('outlook'); + }); + + it('renders status badges and cancel action', () => { + const reset = vi.fn(); + useOAuthFlow.mockReturnValue({ + state: { status: 'awaiting_callback', provider: 'google', connection: { email: 'me@x.com' } }, + initiateAuth: vi.fn(), + checkConnection: vi.fn(), + disconnect: vi.fn(), + reset, + }); + + render( + + ); + + expect(screen.getByText('Awaiting authorization...')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /cancel/i })); + expect(reset).toHaveBeenCalled(); + }); +}); diff --git a/client/src/components/features/calendar/calendar-events-panel.test.tsx b/client/src/components/features/calendar/calendar-events-panel.test.tsx new file mode 100644 index 0000000..bb81506 --- /dev/null +++ b/client/src/components/features/calendar/calendar-events-panel.test.tsx @@ -0,0 +1,101 @@ +import { render, screen } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { CalendarEventsPanel } from '@/components/features/calendar/calendar-events-panel'; +import type { CalendarEvent } from '@/api/types'; +import type { CalendarSyncState } from '@/hooks/sync/use-calendar-sync'; + +const mockFetchEvents = vi.fn(); +const mockStartAutoRefresh = vi.fn(); +const mockStopAutoRefresh = vi.fn(); + +let mockState: CalendarSyncState; +let mockIsAutoRefreshing = false; + +vi.mock('@/hooks', () => ({ + useCalendarSync: () => ({ + state: mockState, + fetchEvents: mockFetchEvents, + startAutoRefresh: mockStartAutoRefresh, + stopAutoRefresh: mockStopAutoRefresh, + isAutoRefreshing: mockIsAutoRefreshing, + }), +})); + +describe('CalendarEventsPanel', () => { + beforeEach(() => { + mockState = { + status: 'idle', + events: [], + providers: [], + error: null, + lastSync: null, + }; + mockIsAutoRefreshing = false; + mockFetchEvents.mockClear(); + mockStartAutoRefresh.mockClear(); + mockStopAutoRefresh.mockClear(); + }); + + afterEach(() => { + mockFetchEvents.mockClear(); + }); + + it('renders empty state when no events', () => { + render(); + + expect(screen.getByText('No upcoming events')).toBeInTheDocument(); + expect(mockFetchEvents).toHaveBeenCalled(); + }); + + it('renders error state', () => { + mockState = { + status: 'error', + events: [], + providers: [], + error: 'Boom', + lastSync: null, + }; + render(); + + expect(screen.getByText('Boom')).toBeInTheDocument(); + }); + + it('renders events list with details', () => { + const now = Math.floor(Date.now() / 1000); + const event: CalendarEvent = { + id: 'e1', + title: 'Team Sync', + start_time: now + 3600, + end_time: now + 7200, + attendees: ['a@example.com', 'b@example.com'], + meeting_url: 'https://meet.example.com', + location: 'Room 1', + is_recurring: true, + }; + + mockState = { + status: 'success', + events: [event], + providers: [], + error: null, + lastSync: null, + }; + + render(); + + expect(screen.getByText('Team Sync')).toBeInTheDocument(); + expect(screen.getByText(/attendee/)).toBeInTheDocument(); + expect(screen.getByText('Video call')).toBeInTheDocument(); + expect(screen.getByText('Room 1')).toBeInTheDocument(); + expect(screen.getByText('Recurring')).toBeInTheDocument(); + }); + + it('shows auto-refresh status when enabled', () => { + mockIsAutoRefreshing = true; + render(); + + expect(mockStartAutoRefresh).toHaveBeenCalled(); + expect(screen.getByText(/Auto-refreshing every 1 minutes/)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/features/calendar/upcoming-meetings.test.tsx b/client/src/components/features/calendar/upcoming-meetings.test.tsx new file mode 100644 index 0000000..8dec570 --- /dev/null +++ b/client/src/components/features/calendar/upcoming-meetings.test.tsx @@ -0,0 +1,299 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { UpcomingMeetings } from './upcoming-meetings'; + +type CalendarEvent = { + id: string; + title: string; + start_time: number; + end_time: number; + location?: string; + attendees?: string[]; + meeting_link?: string; + calendar_name?: string; + provider?: string; +}; + +const mocks = vi.hoisted(() => ({ + useCalendarSync: vi.fn(), + useMeetingReminders: vi.fn(), + preferences: { + getIntegrations: vi.fn(), + }, +})); + +vi.mock('@/hooks', () => ({ + useCalendarSync: mocks.useCalendarSync, + useMeetingReminders: mocks.useMeetingReminders, +})); + +vi.mock('@/lib/preferences', () => ({ + preferences: mocks.preferences, +})); + +vi.mock('@/components/ui/badge', () => ({ + Badge: ({ children }: { children: React.ReactNode }) => {children}, +})); + +vi.mock('@/components/ui/button', () => ({ + Button: ({ + children, + onClick, + disabled, + }: { + children: React.ReactNode; + onClick?: () => void; + disabled?: boolean; + }) => ( + + ), +})); + +vi.mock('@/components/ui/card', () => ({ + Card: ({ children }: { children: React.ReactNode }) =>
{children}
, + CardHeader: ({ children }: { children: React.ReactNode }) =>
{children}
, + CardTitle: ({ children }: { children: React.ReactNode }) =>

{children}

, + CardDescription: ({ children }: { children: React.ReactNode }) =>

{children}

, + CardContent: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock('@/components/ui/checkbox', () => ({ + Checkbox: ({ + checked, + onCheckedChange, + id, + }: { + checked?: boolean; + onCheckedChange?: (next: boolean) => void; + id?: string; + }) => ( +
); } - -/** Summary text for the accordion trigger. */ -export function getModelAuthSummary(status: HuggingFaceTokenStatus | null): string { - if (!status) { - return 'Loading...'; - } - if (status.isConfigured) { - return status.isValidated - ? `Token configured for ${status.username}` - : 'Token configured (not validated)'; - } - return 'No HuggingFace token configured'; -} diff --git a/client/src/components/features/settings/advanced-local-ai-settings/streaming-config-section.tsx b/client/src/components/features/settings/advanced-local-ai-settings/streaming-config-section.tsx index a484640..cb5a45f 100644 --- a/client/src/components/features/settings/advanced-local-ai-settings/streaming-config-section.tsx +++ b/client/src/components/features/settings/advanced-local-ai-settings/streaming-config-section.tsx @@ -331,10 +331,3 @@ export function StreamingConfigSection({
); } - -export function getStreamingConfigSummary(config: StreamingConfiguration | null): string { - if (!config) { - return 'Unavailable'; - } - return `Partials ${config.partialCadenceSeconds}s · Max segment ${config.maxSegmentDurationSeconds}s`; -} diff --git a/client/src/components/features/settings/advanced-local-ai-settings/summaries.ts b/client/src/components/features/settings/advanced-local-ai-settings/summaries.ts new file mode 100644 index 0000000..f2c01e4 --- /dev/null +++ b/client/src/components/features/settings/advanced-local-ai-settings/summaries.ts @@ -0,0 +1,29 @@ +import type { ASRConfiguration, HuggingFaceTokenStatus, StreamingConfiguration } from '@/api/types'; + +export function getModelAuthSummary(status: HuggingFaceTokenStatus | null): string { + if (!status) { + return 'Loading...'; + } + if (status.isConfigured) { + return status.isValidated + ? `Token configured for ${status.username}` + : 'Token configured (not validated)'; + } + return 'No HuggingFace token configured'; +} + +export function getStreamingConfigSummary(config: StreamingConfiguration | null): string { + if (!config) { + return 'Streaming configuration unavailable'; + } + return `Partials every ${config.partialCadenceSeconds.toFixed(1)}s`; +} + +export function getTranscriptionEngineSummary(config: ASRConfiguration | null): string { + if (!config) { + return 'Transcription configuration unavailable'; + } + const deviceLabel = config.device?.toUpperCase() ?? 'Unknown device'; + const modelLabel = config.model || 'Unknown model'; + return `${modelLabel} on ${deviceLabel}`; +} diff --git a/client/src/components/features/settings/advanced-local-ai-settings/transcription-engine-section.tsx b/client/src/components/features/settings/advanced-local-ai-settings/transcription-engine-section.tsx index b6445b9..5d32f19 100644 --- a/client/src/components/features/settings/advanced-local-ai-settings/transcription-engine-section.tsx +++ b/client/src/components/features/settings/advanced-local-ai-settings/transcription-engine-section.tsx @@ -391,11 +391,3 @@ export function TranscriptionEngineSection({ ); } - -/** Summary text for the accordion trigger. */ -export function getTranscriptionEngineSummary(config: ASRConfiguration | null): string { - if (!config) { - return 'Not configured'; - } - return `Model: ${config.modelSize} • Device: ${DEVICE_LABELS[config.device]}`; -} diff --git a/client/src/components/features/settings/export-ai-section.test.tsx b/client/src/components/features/settings/export-ai-section.test.tsx index 6e9212a..7441317 100644 --- a/client/src/components/features/settings/export-ai-section.test.tsx +++ b/client/src/components/features/settings/export-ai-section.test.tsx @@ -1,8 +1,17 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ExportAISection } from './export-ai-section'; +const mocks = vi.hoisted(() => ({ + getTauriExportLocationCache: vi.fn(), + setTauriExportLocationCache: vi.fn(), + isTauriEnvironment: vi.fn(), + addClientLog: vi.fn(), + documentDir: vi.fn(), + join: vi.fn(), +})); + vi.mock('@/components/ui/select', () => ({ Select: ({ children }: { children: ReactNode }) =>
{children}
, SelectTrigger: ({ children }: { children: ReactNode }) =>
{children}
, @@ -11,6 +20,28 @@ vi.mock('@/components/ui/select', () => ({ SelectItem: ({ children }: { children: ReactNode }) =>
{children}
, })); +vi.mock('@/lib/preferences', () => ({ + getTauriExportLocationCache: mocks.getTauriExportLocationCache, + setTauriExportLocationCache: mocks.setTauriExportLocationCache, +})); + +vi.mock('@/api', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isTauriEnvironment: mocks.isTauriEnvironment, + }; +}); + +vi.mock('@tauri-apps/api/path', () => ({ + documentDir: mocks.documentDir, + join: mocks.join, +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: mocks.addClientLog, +})); + // Mock framer-motion to avoid animation issues in tests vi.mock('framer-motion', () => ({ motion: { @@ -38,6 +69,10 @@ describe('ExportAISection', () => { beforeEach(() => { vi.clearAllMocks(); + mocks.getTauriExportLocationCache.mockReturnValue(null); + mocks.isTauriEnvironment.mockReturnValue(false); + mocks.documentDir.mockResolvedValue('/Users/test/Documents'); + mocks.join.mockResolvedValue('/Users/test/Documents/NoteFlow'); }); describe('Component Rendering', () => { @@ -153,4 +188,86 @@ describe('ExportAISection', () => { expect(screen.getByText('Verbosity')).toBeInTheDocument(); }); }); + + describe('OS-specific hints', () => { + it('uses cached Windows path for hint and placeholder', () => { + mocks.getTauriExportLocationCache.mockReturnValue( + 'C:\\\\Users\\\\Me\\\\Documents\\\\NoteFlow' + ); + render(); + + expect( + screen.getByPlaceholderText('C:\\\\Users\\\\Me\\\\Documents\\\\NoteFlow') + ).toBeInTheDocument(); + expect( + screen.getByText( + 'Windows: C:\\\\Users\\\\Me\\\\Documents\\\\NoteFlow (~/Documents/NoteFlow also works).' + ) + ).toBeInTheDocument(); + }); + + it('uses cached macOS path for hint', () => { + mocks.getTauriExportLocationCache.mockReturnValue('/Users/me/Documents/NoteFlow'); + render(); + expect( + screen.getByText('macOS: /Users/me/Documents/NoteFlow.') + ).toBeInTheDocument(); + }); + + it('uses cached Linux path for hint', () => { + mocks.getTauriExportLocationCache.mockReturnValue('/home/me/Documents/NoteFlow'); + render(); + expect( + screen.getByText('Linux: /home/me/Documents/NoteFlow.') + ).toBeInTheDocument(); + }); + + it('falls back to browser detection when no cached path', () => { + Object.defineProperty(window.navigator, 'userAgentData', { + value: { platform: 'Windows' }, + configurable: true, + }); + Object.defineProperty(window.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + + render(); + expect(screen.getByText((text) => text.startsWith('Windows:'))).toBeInTheDocument(); + }); + }); + + describe('Tauri path hydration', () => { + it('loads tauri export location when available', async () => { + mocks.isTauriEnvironment.mockReturnValue(true); + mocks.documentDir.mockResolvedValue('/Users/test/Documents'); + mocks.join.mockResolvedValue('/Users/test/Documents/NoteFlow'); + + render(); + + await waitFor(() => + expect( + screen.getByText('macOS: /Users/test/Documents/NoteFlow.') + ).toBeInTheDocument() + ); + expect(mocks.setTauriExportLocationCache).toHaveBeenCalledWith( + '/Users/test/Documents/NoteFlow' + ); + }); + + it('logs when tauri path APIs are unavailable', async () => { + mocks.isTauriEnvironment.mockReturnValue(true); + mocks.documentDir.mockRejectedValue(new Error('nope')); + + render(); + + await waitFor(() => + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Tauri path APIs unavailable - using browser detection for export location', + }) + ) + ); + }); + }); }); diff --git a/client/src/components/features/settings/integrations-section/helpers.test.ts b/client/src/components/features/settings/integrations-section/helpers.test.ts new file mode 100644 index 0000000..640af4f --- /dev/null +++ b/client/src/components/features/settings/integrations-section/helpers.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import type { Integration } from '@/api/types'; +import { getCalendarProvider, groupIntegrationsByType } from './helpers'; + +const buildIntegration = (overrides: Partial): Integration => ({ + id: 'id', + name: 'Custom', + type: 'custom', + status: 'disconnected', + ...overrides, +}); + +describe('integration helpers', () => { + it('detects calendar providers by name', () => { + expect(getCalendarProvider(buildIntegration({ name: 'Google Calendar' }))).toBe('google'); + expect(getCalendarProvider(buildIntegration({ name: 'Microsoft Outlook' }))).toBe('outlook'); + expect(getCalendarProvider(buildIntegration({ name: 'Outlook Sync' }))).toBe('outlook'); + expect(getCalendarProvider(buildIntegration({ name: 'Other' }))).toBeNull(); + }); + + it('groups integrations by type', () => { + const integrations = [ + buildIntegration({ id: 'auth', type: 'auth' }), + buildIntegration({ id: 'email', type: 'email' }), + buildIntegration({ id: 'calendar', type: 'calendar' }), + buildIntegration({ id: 'pkm', type: 'pkm' }), + buildIntegration({ id: 'oidc', type: 'oidc' }), + buildIntegration({ id: 'custom', type: 'custom' }), + ]; + + const grouped = groupIntegrationsByType(integrations); + + expect(grouped.auth).toHaveLength(1); + expect(grouped.email).toHaveLength(1); + expect(grouped.calendar).toHaveLength(1); + expect(grouped.pkm).toHaveLength(1); + expect(grouped.oidc).toHaveLength(1); + expect(grouped.custom).toHaveLength(1); + }); +}); diff --git a/client/src/components/features/settings/integrations-section/index.tsx b/client/src/components/features/settings/integrations-section/index.tsx index 96e3c94..04254bb 100644 --- a/client/src/components/features/settings/integrations-section/index.tsx +++ b/client/src/components/features/settings/integrations-section/index.tsx @@ -13,7 +13,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { CustomIntegrationDialog, TestAllButton } from './custom-integration-dialog'; import { groupIntegrationsByType } from './helpers'; -import { IntegrationSettingsProvider } from './integration-settings-context'; +import { IntegrationSettingsProvider } from './integration-settings-provider'; import { IntegrationItem } from './integration-item'; import type { Integration } from '@/api/types'; import type { IntegrationsSectionProps } from './types'; @@ -46,7 +46,7 @@ export function IntegrationsSection({ const handleTestIntegrationWithState = useCallback( (integration: Parameters[0]) => handleTestIntegration(integration, setTestingIntegration), - [handleTestIntegration, setTestingIntegration] + [handleTestIntegration] ); const contextValue = useMemo( diff --git a/client/src/components/features/settings/integrations-section/integration-settings-context.tsx b/client/src/components/features/settings/integrations-section/integration-settings-context.ts similarity index 72% rename from client/src/components/features/settings/integrations-section/integration-settings-context.tsx rename to client/src/components/features/settings/integrations-section/integration-settings-context.ts index f514d74..53867f7 100644 --- a/client/src/components/features/settings/integrations-section/integration-settings-context.tsx +++ b/client/src/components/features/settings/integrations-section/integration-settings-context.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; -import type { MutableRefObject, ReactNode } from 'react'; +import type { MutableRefObject } from 'react'; import type { Integration } from '@/api/types'; import type { OAuthFlowState } from '@/hooks'; @@ -20,21 +20,8 @@ export interface IntegrationSettingsContextValue { removeIntegration: (integrationId: string) => void; } -const IntegrationSettingsContext = createContext(null); - -export function IntegrationSettingsProvider({ - value, - children, -}: { - value: IntegrationSettingsContextValue; - children: ReactNode; -}) { - return ( - - {children} - - ); -} +export const IntegrationSettingsContext = + createContext(null); export function useIntegrationSettingsContext(): IntegrationSettingsContextValue { const context = useContext(IntegrationSettingsContext); diff --git a/client/src/components/features/settings/integrations-section/integration-settings-provider.tsx b/client/src/components/features/settings/integrations-section/integration-settings-provider.tsx new file mode 100644 index 0000000..b26bb37 --- /dev/null +++ b/client/src/components/features/settings/integrations-section/integration-settings-provider.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react'; +import { + IntegrationSettingsContext, + type IntegrationSettingsContextValue, +} from './integration-settings-context'; + +export function IntegrationSettingsProvider({ + value, + children, +}: { + value: IntegrationSettingsContextValue; + children: ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/client/src/components/features/settings/integrations-section/use-calendar-integration.ts b/client/src/components/features/settings/integrations-section/use-calendar-integration.ts index 341b47e..2a45f98 100644 --- a/client/src/components/features/settings/integrations-section/use-calendar-integration.ts +++ b/client/src/components/features/settings/integrations-section/use-calendar-integration.ts @@ -4,12 +4,13 @@ import { useCallback, useEffect, useRef } from 'react'; -import { getAPI } from '@/api'; +import { getAPI, IdentityDefaults } from '@/api'; import type { Integration } from '@/api/types'; import { useWorkspace } from '@/contexts/workspace-state'; import { useOAuthFlow } from '@/hooks'; import { toast } from '@/hooks'; import { debug } from '@/lib/observability/debug'; +import { toastError } from '@/lib/observability/errors'; import { preferences } from '@/lib/preferences'; import { getCalendarProvider } from './helpers'; @@ -20,7 +21,7 @@ interface UseCalendarIntegrationProps { } export function useCalendarIntegration({ - integrations, + integrations: _integrations, setIntegrations, }: UseCalendarIntegrationProps) { const log = debug('CalendarIntegration'); @@ -122,7 +123,7 @@ export function useCalendarIntegration({ return () => { cancelled = true; }; - }, [setIntegrations, workspaceId]); + }, [log, setIntegrations, workspaceId]); const syncCalendarOAuthConfig = useCallback( async (integration: Integration) => { @@ -228,4 +229,4 @@ export function useCalendarIntegration({ handleCalendarConnect, handleCalendarDisconnect, }; -} \ No newline at end of file +} diff --git a/client/src/components/features/settings/integrations-section/use-integration-crud.ts b/client/src/components/features/settings/integrations-section/use-integration-crud.ts index 474f407..a9ac984 100644 --- a/client/src/components/features/settings/integrations-section/use-integration-crud.ts +++ b/client/src/components/features/settings/integrations-section/use-integration-crud.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; +import { IdentityDefaults } from '@/api'; import type { Integration } from '@/api/types'; import { useWorkspace } from '@/contexts/workspace-state'; import { useOidcProviders } from '@/hooks'; @@ -20,7 +21,7 @@ interface UseIntegrationCrudProps { } export function useIntegrationCrud({ - integrations, + integrations: _integrations, setIntegrations, }: UseIntegrationCrudProps) { const { currentWorkspace } = useWorkspace(); @@ -135,4 +136,4 @@ export function useIntegrationCrud({ handleAddCustomIntegration, handleUpdateIntegrationConfig, }; -} \ No newline at end of file +} diff --git a/client/src/components/features/tasks/TasksKanbanView.tsx b/client/src/components/features/tasks/TasksKanbanView.tsx new file mode 100644 index 0000000..c60064b --- /dev/null +++ b/client/src/components/features/tasks/TasksKanbanView.tsx @@ -0,0 +1,363 @@ +import { + DndContext, + DragOverlay, + type DragEndEvent, + type DragStartEvent, + useSensor, + useSensors, + PointerSensor, + closestCorners, +} from '@dnd-kit/core'; +import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Calendar, CheckCircle2, Circle, GripVertical, XCircle } from 'lucide-react'; +import { useMemo, useState } from 'react'; +import { Link } from 'react-router-dom'; + +import type { TaskStatus, TaskWithMeeting } from '@/api/types'; +import { PriorityBadge } from '@/components/common'; +import { Card, CardContent } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { cn } from '@/lib/utils'; +import { formatRelativeTime } from '@/lib/utils/format'; + +interface TasksKanbanViewProps { + tasks: TaskWithMeeting[]; + onStatusChange: (taskId: string, newStatus: TaskStatus) => void; + onTextChange: (taskId: string, newText: string) => void; + isUpdating: boolean; + getTaskMeetingLink: (task: TaskWithMeeting) => string; +} + +type ColumnId = TaskStatus; + +const COLUMNS: { id: ColumnId; label: string; icon: React.ReactNode }[] = [ + { id: 'open', label: 'Open', icon: }, + { id: 'done', label: 'Done', icon: }, + { id: 'dismissed', label: 'Dismissed', icon: }, +]; + +export function TasksKanbanView({ + tasks, + onStatusChange, + onTextChange, + isUpdating, + getTaskMeetingLink, +}: TasksKanbanViewProps) { + const [activeId, setActiveId] = useState(null); + + // Group tasks by status + const tasksByStatus = useMemo(() => { + const grouped: Record = { + open: [], + done: [], + dismissed: [], + }; + + tasks.forEach((task) => { + if (task.task.status in grouped) { + grouped[task.task.status].push(task); + } + }); + + return grouped; + }, [tasks]); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, + }, + }) + ); + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id as string); + }; + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + setActiveId(null); + + if (!over) return; + + const taskId = active.id as string; + const overId = over.id as string; + + const task = tasks.find((t) => t.task.id === taskId); + if (!task) return; + + if (COLUMNS.some((col) => col.id === overId)) { + const newStatus = overId as TaskStatus; + if (newStatus !== task.task.status) { + onStatusChange(taskId, newStatus); + } + return; + } + + const overTask = tasks.find((t) => t.task.id === overId); + if (overTask && overTask.task.status !== task.task.status) { + onStatusChange(taskId, overTask.task.status); + } + }; + + const activeTask = activeId ? tasks.find((t) => t.task.id === activeId) : null; + + return ( + +
+ {COLUMNS.map((column) => ( + + ))} +
+ + + {activeTask ? ( +
+ +
+ ) : null} +
+
+ ); +} + +interface KanbanColumnProps { + id: ColumnId; + title: string; + icon: React.ReactNode; + tasks: TaskWithMeeting[]; + getTaskMeetingLink: (task: TaskWithMeeting) => string; + onTextChange: (taskId: string, newText: string) => void; + isUpdating?: boolean; +} + +function KanbanColumn({ + id, + title, + icon, + tasks, + getTaskMeetingLink, + onTextChange, + isUpdating, +}: KanbanColumnProps) { + const { setNodeRef } = useSortable({ + id, + data: { + type: 'Column', + status: id, + }, + disabled: true, + }); + + return ( +
+
+
+ {icon} + {title} + + {tasks.length} + +
+
+ +
+ t.task.id)} + strategy={verticalListSortingStrategy} + > + {tasks.map((task) => ( + + ))} + + {tasks.length === 0 && ( +
+ Drop tasks here +
+ )} +
+
+ ); +} + +interface KanbanTaskCardProps { + taskWithMeeting: TaskWithMeeting; + getTaskMeetingLink: (task: TaskWithMeeting) => string; + onTextChange: (taskId: string, newText: string) => void; + isOverlay?: boolean; + isUpdating?: boolean; +} + +function KanbanTaskCard({ + taskWithMeeting, + getTaskMeetingLink, + onTextChange, + isOverlay, + isUpdating, +}: KanbanTaskCardProps) { + const { task } = taskWithMeeting; + const [isEditing, setIsEditing] = useState(false); + const [editText, setEditText] = useState(task.text); + + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: task.id, + data: { + type: 'Task', + task: taskWithMeeting, + }, + disabled: isEditing || isUpdating, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + const handleEditSubmit = () => { + if (editText.trim() && editText !== task.text) { + onTextChange(task.id, editText); + } else { + setEditText(task.text); + } + setIsEditing(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleEditSubmit(); + } else if (e.key === 'Escape') { + setEditText(task.text); + setIsEditing(false); + } + }; + + const getPriorityLabel = (p: number) => { + switch (p) { + case 3: return 'high'; + case 2: return 'medium'; + case 1: return 'low'; + default: return 'medium'; + } + }; + + if (isDragging) { + return ( +
+ ); + } + + return ( +
+ + +
+
+ {isEditing ? ( + setEditText(e.target.value)} + onBlur={handleEditSubmit} + onKeyDown={handleKeyDown} + autoFocus + disabled={isUpdating} + className="h-7 text-sm px-1 py-0.5" + /> + ) : ( +

!isUpdating && setIsEditing(true)} + title="Double-click to edit" + > + {task.text} +

+ )} +
+
+ +
+
+ +
+
+ e.stopPropagation()} + title={taskWithMeeting.meeting_title} + > + + {taskWithMeeting.meeting_title} + +
+ +
+ {formatRelativeTime(taskWithMeeting.meeting_created_at)} + +
+
+
+
+
+ ); +} diff --git a/client/src/components/ui/icon-circle.tsx b/client/src/components/ui/icon-circle.tsx index 6b18b36..cc60252 100644 --- a/client/src/components/ui/icon-circle.tsx +++ b/client/src/components/ui/icon-circle.tsx @@ -32,7 +32,7 @@ const iconCircleVariants = cva('inline-flex items-center justify-center rounded- }, }); -export interface IconCircleProps +interface IconCircleProps extends React.HTMLAttributes, VariantProps { /** Icon or content to display */ @@ -70,4 +70,4 @@ const IconCircle = React.forwardRef( ); IconCircle.displayName = 'IconCircle'; -export { IconCircle, iconCircleVariants }; +export { IconCircle }; diff --git a/client/src/components/ui/markdown-editor-utils.ts b/client/src/components/ui/markdown-editor-utils.ts new file mode 100644 index 0000000..c9cbbe3 --- /dev/null +++ b/client/src/components/ui/markdown-editor-utils.ts @@ -0,0 +1,114 @@ +import { useEffect } from 'react'; + +import { useEditor, type Editor } from '@tiptap/react'; +import Placeholder from '@tiptap/extension-placeholder'; +import StarterKit from '@tiptap/starter-kit'; +import TaskItem from '@tiptap/extension-task-item'; +import TaskList from '@tiptap/extension-task-list'; +import { Markdown } from 'tiptap-markdown'; + +import { cn } from '@/lib/utils'; + +interface MarkdownStorage { + getMarkdown: () => string; +} + +/** + * Type-safe accessor for tiptap-markdown storage. + * The Markdown extension adds this storage at runtime. + */ +function getMarkdownStorage(editor: Editor): MarkdownStorage { + const storage = editor.storage; + const markdown = (storage as Record).markdown; + if (markdown && typeof markdown === 'object' && 'getMarkdown' in markdown) { + return markdown as MarkdownStorage; + } + return { getMarkdown: () => '' }; +} + +export interface UseMarkdownEditorOptions { + /** Initial content as markdown string */ + content?: string; + /** Placeholder text */ + placeholder?: string; + /** Called when content changes */ + onChange?: (markdown: string) => void; + /** Whether editor is editable */ + editable?: boolean; +} + +/** + * Hook to create and manage a TipTap editor instance with markdown support. + */ +export function useMarkdownEditor({ + content = '', + placeholder = '', + onChange, + editable = true, +}: UseMarkdownEditorOptions = {}): Editor | null { + const editor = useEditor({ + extensions: [ + StarterKit.configure({ + bulletList: { keepMarks: true }, + orderedList: { keepMarks: true }, + }), + TaskList, + TaskItem.configure({ + nested: true, + }), + Placeholder.configure({ + placeholder, + emptyEditorClass: 'is-editor-empty', + }), + Markdown.configure({ + html: false, + transformPastedText: true, + transformCopiedText: true, + }), + ], + content, + editable, + editorProps: { + attributes: { + class: cn( + 'prose prose-sm dark:prose-invert max-w-none', + 'focus:outline-none min-h-[100px] px-3 py-2', + '[&_ul[data-type="taskList"]]:list-none [&_ul[data-type="taskList"]]:pl-0', + '[&_ul[data-type="taskList"]_li]:flex [&_ul[data-type="taskList"]_li]:gap-2 [&_ul[data-type="taskList"]_li]:items-start', + '[&_ul[data-type="taskList"]_li_label]:mt-0.5', + '[&_ul[data-type="taskList"]_li_div]:flex-1' + ), + }, + }, + onUpdate: ({ editor: e }) => { + if (onChange) { + const markdown = getMarkdownStorage(e).getMarkdown(); + onChange(markdown); + } + }, + }); + + useEffect(() => { + if (editor && content !== getMarkdownStorage(editor).getMarkdown()) { + editor.commands.setContent(content); + } + }, [editor, content]); + + useEffect(() => { + if (editor) { + editor.setEditable(editable); + } + }, [editor, editable]); + + return editor; +} + +/** + * Helper to safely get markdown content from an editor instance. + */ +export function getEditorMarkdown(editor: Editor | null): string { + if (!editor) { + return ''; + } + return getMarkdownStorage(editor).getMarkdown(); +} diff --git a/client/src/components/ui/markdown-editor.test.tsx b/client/src/components/ui/markdown-editor.test.tsx new file mode 100644 index 0000000..8419e29 --- /dev/null +++ b/client/src/components/ui/markdown-editor.test.tsx @@ -0,0 +1,187 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { MarkdownEditor } from '@/components/ui/markdown-editor'; + +type Chain = { + focus: () => Chain; + toggleBold: () => Chain; + toggleItalic: () => Chain; + toggleCode: () => Chain; + toggleBulletList: () => Chain; + toggleOrderedList: () => Chain; + toggleBlockquote: () => Chain; + undo: () => Chain; + redo: () => Chain; + run: () => void; +}; + +type EditorStub = { + id: string; + isActive: (name: string) => boolean; + can: () => Record boolean>; + chain: () => Chain; + destroy: () => void; +}; + +const mocks = vi.hoisted(() => ({ + useMarkdownEditor: vi.fn(), +})); + +vi.mock('@/components/ui/markdown-editor-utils', () => ({ + useMarkdownEditor: mocks.useMarkdownEditor, +})); + +vi.mock('@tiptap/react', () => ({ + EditorContent: ({ + editor, + className, + }: { + editor: { id?: string } | null; + className?: string; + }) => ( +
+ ), +})); + +vi.mock('@/components/ui/tooltip', () => ({ + Tooltip: ({ children }: { children: React.ReactNode }) =>
{children}
, + TooltipTrigger: ({ children }: { children: React.ReactNode }) =>
{children}
, + TooltipContent: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock('@/components/ui/separator', () => ({ + Separator: () =>
, +})); + +vi.mock('@/components/ui/toggle', () => ({ + Toggle: ({ + children, + onPressedChange, + ...props + }: { + children: React.ReactNode; + onPressedChange: () => void; + }) => ( + + ), +})); + +const createEditor = (id: string): { editor: EditorStub; chain: Chain } => { + const chain: Chain = { + focus: () => chain, + toggleBold: () => chain, + toggleItalic: () => chain, + toggleCode: () => chain, + toggleBulletList: () => chain, + toggleOrderedList: () => chain, + toggleBlockquote: () => chain, + undo: () => chain, + redo: () => chain, + run: vi.fn(), + }; + const editor: EditorStub = { + id, + isActive: () => false, + can: () => ({ + toggleBold: () => true, + toggleItalic: () => true, + toggleCode: () => true, + toggleBulletList: () => true, + toggleOrderedList: () => true, + toggleBlockquote: () => true, + undo: () => true, + redo: () => true, + }), + chain: vi.fn(() => chain), + destroy: vi.fn(), + }; + return { editor, chain }; +}; + +describe('MarkdownEditor', () => { + beforeEach(() => { + mocks.useMarkdownEditor.mockReset(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders toolbar and executes formatting commands', () => { + const { editor, chain } = createEditor('internal'); + mocks.useMarkdownEditor.mockReturnValue(editor); + + render(); + + fireEvent.click(screen.getByLabelText('Bold')); + fireEvent.click(screen.getByLabelText('Italic')); + + expect(editor.chain).toHaveBeenCalled(); + expect(chain.run).toHaveBeenCalled(); + }); + + it('hides toolbar when disabled', () => { + const { editor } = createEditor('internal'); + mocks.useMarkdownEditor.mockReturnValue(editor); + + render(); + + expect(screen.queryByLabelText('Bold')).toBeNull(); + expect(screen.getByTestId('editor-content')).toBeInTheDocument(); + }); + + it('skips toolbar when editor is null', () => { + mocks.useMarkdownEditor.mockReturnValue(null); + + render(); + + expect(screen.queryByLabelText('Bold')).toBeNull(); + expect(screen.getByTestId('editor-content')).toHaveAttribute('data-editor-id', 'none'); + }); + + it('uses external editor when provided', () => { + const { editor } = createEditor('external'); + mocks.useMarkdownEditor.mockReturnValue(createEditor('internal').editor); + + render(); + + expect(screen.getByTestId('editor-content')).toHaveAttribute('data-editor-id', 'external'); + }); + + it('destroys internal editor on unmount', () => { + const { editor } = createEditor('internal'); + mocks.useMarkdownEditor.mockReturnValue(editor); + + const { unmount } = render(); + unmount(); + + expect(editor.destroy).toHaveBeenCalled(); + }); + + it('does not destroy external editor on unmount', () => { + const { editor } = createEditor('external'); + mocks.useMarkdownEditor.mockReturnValue(createEditor('internal').editor); + + const { unmount } = render(); + unmount(); + + expect(editor.destroy).not.toHaveBeenCalled(); + }); + + it('passes editable false when disabled', () => { + const { editor } = createEditor('internal'); + mocks.useMarkdownEditor.mockReturnValue(editor); + + render(); + + expect(mocks.useMarkdownEditor).toHaveBeenCalledWith( + expect.objectContaining({ editable: false }) + ); + }); +}); diff --git a/client/src/components/ui/markdown-editor.tsx b/client/src/components/ui/markdown-editor.tsx index ea87e7a..819a4a2 100644 --- a/client/src/components/ui/markdown-editor.tsx +++ b/client/src/components/ui/markdown-editor.tsx @@ -8,49 +8,15 @@ import { Bold, Code, Italic, List, ListOrdered, Quote, Redo, Undo } from 'lucide-react'; import { useEffect } from 'react'; -import { EditorContent, useEditor, type Editor } from '@tiptap/react'; -import Placeholder from '@tiptap/extension-placeholder'; -import StarterKit from '@tiptap/starter-kit'; -import TaskItem from '@tiptap/extension-task-item'; -import TaskList from '@tiptap/extension-task-list'; -import { Markdown } from 'tiptap-markdown'; +import { EditorContent, type Editor } from '@tiptap/react'; import { Separator } from '@/components/ui/separator'; import { Toggle } from '@/components/ui/toggle'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { cn } from '@/lib/utils'; +import { useMarkdownEditor } from '@/components/ui/markdown-editor-utils'; -// --------------------------------------------------------------------------- -// Type-safe markdown storage access -// --------------------------------------------------------------------------- - -interface MarkdownStorage { - getMarkdown: () => string; -} - -/** - * Type-safe accessor for tiptap-markdown storage. - * The Markdown extension adds this storage at runtime. - * - * TipTap's Storage type is `{}` but extensions add properties dynamically. - * We use bracket notation and runtime validation for safe access. - */ -function getMarkdownStorage(editor: Editor): MarkdownStorage { - const storage = editor.storage; - // Access via bracket notation to avoid TS structural typing issues - const markdown = (storage as Record)['markdown']; - if (markdown && typeof markdown === 'object' && 'getMarkdown' in markdown) { - return markdown as MarkdownStorage; - } - // Fallback (should never happen when Markdown extension is installed) - return { getMarkdown: () => '' }; -} - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -export interface MarkdownEditorProps { +interface MarkdownEditorProps { /** Initial content as markdown string */ content?: string; /** Placeholder text when editor is empty */ @@ -67,91 +33,6 @@ export interface MarkdownEditorProps { editor?: Editor | null; } -export interface UseMarkdownEditorOptions { - /** Initial content as markdown string */ - content?: string; - /** Placeholder text */ - placeholder?: string; - /** Called when content changes */ - onChange?: (markdown: string) => void; - /** Whether editor is editable */ - editable?: boolean; -} - -// --------------------------------------------------------------------------- -// Hook: useMarkdownEditor -// --------------------------------------------------------------------------- - -/** - * Hook to create and manage a TipTap editor instance with markdown support. - * Use this when you need external access to the editor (e.g., for custom toolbars). - */ -export function useMarkdownEditor({ - content = '', - placeholder = '', - onChange, - editable = true, -}: UseMarkdownEditorOptions = {}): Editor | null { - const editor = useEditor({ - extensions: [ - StarterKit.configure({ - // Disable default task list since we're using the dedicated extension - bulletList: { keepMarks: true }, - orderedList: { keepMarks: true }, - }), - TaskList, - TaskItem.configure({ - nested: true, - }), - Placeholder.configure({ - placeholder, - emptyEditorClass: 'is-editor-empty', - }), - Markdown.configure({ - html: false, // Don't allow raw HTML - transformPastedText: true, // Parse markdown on paste - transformCopiedText: true, // Copy as markdown - }), - ], - content, - editable, - editorProps: { - attributes: { - class: cn( - 'prose prose-sm dark:prose-invert max-w-none', - 'focus:outline-none min-h-[100px] px-3 py-2', - '[&_ul[data-type="taskList"]]:list-none [&_ul[data-type="taskList"]]:pl-0', - '[&_ul[data-type="taskList"]_li]:flex [&_ul[data-type="taskList"]_li]:gap-2 [&_ul[data-type="taskList"]_li]:items-start', - '[&_ul[data-type="taskList"]_li_label]:mt-0.5', - '[&_ul[data-type="taskList"]_li_div]:flex-1' - ), - }, - }, - onUpdate: ({ editor: e }) => { - if (onChange) { - const markdown = getMarkdownStorage(e).getMarkdown(); - onChange(markdown); - } - }, - }); - - // Update content when prop changes - useEffect(() => { - if (editor && content !== getMarkdownStorage(editor).getMarkdown()) { - editor.commands.setContent(content); - } - }, [editor, content]); - - // Update editable state - useEffect(() => { - if (editor) { - editor.setEditable(editable); - } - }, [editor, editable]); - - return editor; -} - // --------------------------------------------------------------------------- // Toolbar Button Component // --------------------------------------------------------------------------- @@ -349,17 +230,3 @@ export function MarkdownEditor({
); } - -// --------------------------------------------------------------------------- -// Utility: Get markdown from editor -// --------------------------------------------------------------------------- - -/** - * Helper to safely get markdown content from an editor instance. - */ -export function getEditorMarkdown(editor: Editor | null): string { - if (!editor) { - return ''; - } - return getMarkdownStorage(editor).getMarkdown(); -} diff --git a/client/src/components/ui/sidebar/index.tsx b/client/src/components/ui/sidebar/index.ts similarity index 97% rename from client/src/components/ui/sidebar/index.tsx rename to client/src/components/ui/sidebar/index.ts index a1f5953..5aaba0e 100644 --- a/client/src/components/ui/sidebar/index.tsx +++ b/client/src/components/ui/sidebar/index.ts @@ -44,7 +44,6 @@ export { SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, - sidebarMenuButtonVariants, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, diff --git a/client/src/components/ui/sidebar/menu.tsx b/client/src/components/ui/sidebar/menu.tsx index d276587..b5f149b 100644 --- a/client/src/components/ui/sidebar/menu.tsx +++ b/client/src/components/ui/sidebar/menu.tsx @@ -36,7 +36,7 @@ export const SidebarMenuItem = React.forwardRefspan:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', { variants: { diff --git a/client/src/components/ui/slider.test.tsx b/client/src/components/ui/slider.test.tsx new file mode 100644 index 0000000..3ba12ad --- /dev/null +++ b/client/src/components/ui/slider.test.tsx @@ -0,0 +1,29 @@ +import { render } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Slider } from './slider'; + +describe('Slider', () => { + beforeEach(() => { + vi.stubGlobal( + 'ResizeObserver', + class { + observe() {} + unobserve() {} + disconnect() {} + } + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('renders with default classes and custom className', () => { + const { container } = render( + + ); + + const root = container.querySelector('.custom-slider'); + expect(root).not.toBeNull(); + }); +}); diff --git a/client/src/contexts/project-context.tsx b/client/src/contexts/project-context.tsx index 0c760d2..df90b2c 100644 --- a/client/src/contexts/project-context.tsx +++ b/client/src/contexts/project-context.tsx @@ -113,6 +113,9 @@ export function ProjectProvider({ children }: { children: React.ReactNode }) { // Reload projects when workspace ID changes (not on every workspace object reference change) const workspaceId = currentWorkspace?.id; useEffect(() => { + if (!workspaceId) { + return; + } void loadProjects(); }, [loadProjects, workspaceId]); diff --git a/client/src/contexts/storage.test.ts b/client/src/contexts/storage.test.ts new file mode 100644 index 0000000..e33f4be --- /dev/null +++ b/client/src/contexts/storage.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from 'vitest'; +import { + clearStoredProjectIds, + clearStoredWorkspaceId, + projectStorageKey, + workspaceStorageKey, +} from './storage'; +import { + ACTIVE_PROJECT_PREFIX, + CURRENT_WORKSPACE_KEY, + getActiveProjectKey, +} from '@/lib/storage/keys'; +import { clearStorageByPrefix, removeStorage } from '@/lib/storage/utils'; + +vi.mock('@/lib/storage/utils', () => ({ + clearStorageByPrefix: vi.fn(), + removeStorage: vi.fn(), +})); + +describe('storage context helpers', () => { + it('builds project storage keys', () => { + expect(projectStorageKey('workspace-1')).toBe(getActiveProjectKey('workspace-1')); + }); + + it('clears stored project ids', () => { + clearStoredProjectIds(); + expect(clearStorageByPrefix).toHaveBeenCalledWith(ACTIVE_PROJECT_PREFIX, 'clear_project_ids'); + }); + + it('builds workspace storage key', () => { + expect(workspaceStorageKey()).toBe(CURRENT_WORKSPACE_KEY); + }); + + it('clears stored workspace id', () => { + clearStoredWorkspaceId(); + expect(removeStorage).toHaveBeenCalledWith(CURRENT_WORKSPACE_KEY, 'clear_workspace_id'); + }); +}); diff --git a/client/src/hooks/audio/use-streaming-config.test.tsx b/client/src/hooks/audio/use-streaming-config.test.tsx new file mode 100644 index 0000000..28e8816 --- /dev/null +++ b/client/src/hooks/audio/use-streaming-config.test.tsx @@ -0,0 +1,78 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useStreamingConfig } from '@/hooks/audio/use-streaming-config'; + +let mode = 'connected'; +const api = { + getStreamingConfiguration: vi.fn(), + updateStreamingConfiguration: vi.fn(), +}; +const extractErrorMessage = vi.fn((_: unknown, fallback: string) => fallback); + +vi.mock('@/contexts/connection-state', () => ({ + useConnectionState: () => ({ mode }), +})); + +vi.mock('@/api/interface', () => ({ + getAPI: () => api, +})); + +vi.mock('@/api', () => ({ + extractErrorMessage: (...args: unknown[]) => + extractErrorMessage(args[0], args[1] as string), +})); + +describe('useStreamingConfig', () => { + beforeEach(() => { + mode = 'connected'; + api.getStreamingConfiguration.mockReset(); + api.updateStreamingConfiguration.mockReset(); + extractErrorMessage.mockReset(); + }); + + it('loads configuration and updates on success and failure', async () => { + api.getStreamingConfiguration.mockResolvedValueOnce({ partialCadenceSeconds: 1 }); + const { result } = renderHook(() => useStreamingConfig()); + + await waitFor(() => { + expect(result.current.config).toEqual({ partialCadenceSeconds: 1 }); + expect(result.current.isLoading).toBe(false); + }); + + api.updateStreamingConfiguration.mockResolvedValueOnce({ partialCadenceSeconds: 2 }); + let updated = false; + await act(async () => { + updated = await result.current.updateConfig({ partialCadenceSeconds: 2 }); + }); + expect(updated).toBe(true); + expect(result.current.config).toEqual({ partialCadenceSeconds: 2 }); + + api.updateStreamingConfiguration.mockRejectedValueOnce(new Error('fail')); + let failed = true; + await act(async () => { + failed = await result.current.updateConfig({ partialCadenceSeconds: 3 }); + }); + expect(failed).toBe(false); + expect(result.current.error).toBe('Failed to update streaming configuration'); + }); + + it('handles refresh failures and mode change refresh', async () => { + api.getStreamingConfiguration.mockRejectedValueOnce(new Error('fail')); + const { result, rerender } = renderHook(() => useStreamingConfig()); + + await waitFor(() => { + expect(result.current.error).toBe('Failed to load streaming configuration'); + }); + + api.getStreamingConfiguration.mockResolvedValueOnce({ partialCadenceSeconds: 5 }); + mode = 'disconnected'; + rerender(); + mode = 'connected'; + rerender(); + + await waitFor(() => { + expect(api.getStreamingConfiguration).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/client/src/hooks/auth/use-auth-flow.test.tsx b/client/src/hooks/auth/use-auth-flow.test.tsx new file mode 100644 index 0000000..fac3556 --- /dev/null +++ b/client/src/hooks/auth/use-auth-flow.test.tsx @@ -0,0 +1,231 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useAuthFlow } from '@/hooks/auth/use-auth-flow'; + +let isTauri = true; +const api = { + initiateAuthLogin: vi.fn(), + completeAuthLogin: vi.fn(), + getCurrentUser: vi.fn(), + logout: vi.fn(), +}; +const toast = vi.fn(); +const toastError = vi.fn((args: { fallback: string }) => args.fallback); +const addClientLog = vi.fn(); +const openAuthUrl = vi.fn().mockResolvedValue(true); +const setupDeepLinkListener = vi.fn(); +const extractOAuthCallback = vi.fn((url: string) => { + try { + const parsed = new URL(url); + const code = parsed.searchParams.get('code'); + const state = parsed.searchParams.get('state'); + return code && state ? { code, state } : null; + } catch { + return null; + } +}); +const validateOAuthState = vi.fn((received: string, expected: string | null) => { + return expected !== null && received === expected; +}); + +let deepLinkHandler: ((urls: string[]) => void) | undefined; + +vi.mock('@/api/interface', () => ({ + getAPI: () => api, +})); + +vi.mock('@/api', () => ({ + isTauriEnvironment: () => isTauri, +})); + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: (args: unknown) => toast(args), +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: (args: unknown) => addClientLog(args), +})); + +vi.mock('@/lib/observability/errors', () => ({ + toastError: (args: { fallback: string }) => toastError(args), +})); + +vi.mock('@/lib/integrations/oauth', () => ({ + extractOAuthCallback: (url: string) => extractOAuthCallback(url), + openAuthUrl: (url: string) => openAuthUrl(url), + setupDeepLinkListener: (handler: (urls: string[]) => void) => { + deepLinkHandler = handler; + return setupDeepLinkListener(handler); + }, + validateOAuthState: (received: string, expected: string | null) => + validateOAuthState(received, expected), +})); + +describe('useAuthFlow', () => { + beforeEach(() => { + isTauri = true; + deepLinkHandler = undefined; + api.initiateAuthLogin.mockReset(); + api.completeAuthLogin.mockReset(); + api.getCurrentUser.mockReset(); + api.logout.mockReset(); + toast.mockReset(); + toastError.mockReset(); + addClientLog.mockReset(); + openAuthUrl.mockClear(); + setupDeepLinkListener.mockReset(); + setupDeepLinkListener.mockResolvedValue(() => {}); + extractOAuthCallback.mockClear(); + validateOAuthState.mockClear(); + }); + + it('handles initiate login success and failure', async () => { + api.initiateAuthLogin.mockResolvedValueOnce({ auth_url: 'https://auth', state: 'state1' }); + const { result } = renderHook(() => useAuthFlow()); + + await act(async () => { + await result.current.initiateLogin('google'); + }); + + await waitFor(() => { + expect(result.current.state.status).toBe('awaiting_callback'); + }); + expect(openAuthUrl).toHaveBeenCalledWith('https://auth'); + + api.initiateAuthLogin.mockResolvedValueOnce({ auth_url: '', state: null }); + await act(async () => { + await result.current.initiateLogin('google'); + }); + + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('Failed to initiate login'); + }); + + it('completes login and handles errors', async () => { + api.completeAuthLogin.mockResolvedValueOnce({ success: true }); + api.getCurrentUser.mockResolvedValueOnce({ + is_authenticated: true, + auth_provider: 'google', + user_id: 'u1', + display_name: 'User', + }); + + const { result } = renderHook(() => useAuthFlow()); + let success = false; + await act(async () => { + success = await result.current.completeLogin('google', 'code', 'state'); + }); + + expect(success).toBe(true); + await waitFor(() => { + expect(result.current.state.status).toBe('authenticated'); + }); + expect(toast).toHaveBeenCalled(); + + api.completeAuthLogin.mockResolvedValueOnce({ success: false, error_message: 'bad' }); + let failed = true; + await act(async () => { + failed = await result.current.completeLogin('google', 'code', 'state'); + }); + expect(failed).toBe(false); + expect(result.current.state.status).toBe('error'); + }); + + it('checks auth status and logs failures', async () => { + api.getCurrentUser.mockResolvedValueOnce({ + is_authenticated: false, + auth_provider: null, + user_id: 'u1', + display_name: 'User', + }); + const { result } = renderHook(() => useAuthFlow()); + + let user = null; + await act(async () => { + user = await result.current.checkAuthStatus(); + }); + expect(user?.is_authenticated).toBe(false); + expect(result.current.state.status).toBe('idle'); + + api.getCurrentUser.mockRejectedValueOnce(new Error('nope')); + let failed: unknown = undefined; + await act(async () => { + failed = await result.current.checkAuthStatus(); + }); + expect(failed).toBeNull(); + expect(addClientLog).toHaveBeenCalled(); + }); + + it('logs out and handles failures', async () => { + api.logout.mockResolvedValueOnce({ success: true }); + const { result } = renderHook(() => useAuthFlow()); + let loggedOut = false; + await act(async () => { + loggedOut = await result.current.logout('google'); + }); + expect(loggedOut).toBe(true); + expect(result.current.state.status).toBe('idle'); + expect(toast).toHaveBeenCalled(); + + api.logout.mockResolvedValueOnce({ success: false }); + let fail = true; + await act(async () => { + fail = await result.current.logout('google'); + }); + expect(fail).toBe(false); + + api.logout.mockRejectedValueOnce(new Error('boom')); + let error = true; + await act(async () => { + error = await result.current.logout('google'); + }); + expect(error).toBe(false); + expect(toastError).toHaveBeenCalled(); + }); + + it('processes deep link callbacks with success and mismatch', async () => { + setupDeepLinkListener.mockResolvedValueOnce(() => {}); + api.initiateAuthLogin.mockResolvedValueOnce({ auth_url: 'https://auth', state: 'state1' }); + api.completeAuthLogin.mockResolvedValueOnce({ success: true }); + api.getCurrentUser.mockResolvedValueOnce({ + is_authenticated: true, + auth_provider: 'google', + user_id: 'u1', + display_name: 'User', + }); + + const { result } = renderHook(() => useAuthFlow()); + await act(async () => { + await result.current.initiateLogin('google'); + }); + + validateOAuthState.mockReturnValueOnce(false); + act(() => { + deepLinkHandler?.(['noteflow://callback?code=bad&state=wrong']); + }); + expect(toast).toHaveBeenCalled(); + + validateOAuthState.mockReturnValueOnce(true); + await act(async () => { + deepLinkHandler?.(['noteflow://callback?code=good&state=state1']); + }); + + await waitFor(() => { + expect(result.current.state.status).toBe('authenticated'); + }); + }); + + it('skips tauri deep link setup and logs setup errors', async () => { + isTauri = false; + renderHook(() => useAuthFlow()); + expect(setupDeepLinkListener).not.toHaveBeenCalled(); + + isTauri = true; + setupDeepLinkListener.mockRejectedValueOnce(new Error('no listener')); + renderHook(() => useAuthFlow()); + await waitFor(() => { + expect(addClientLog).toHaveBeenCalled(); + }); + }); +}); diff --git a/client/src/hooks/auth/use-secure-integration-secrets.test.tsx b/client/src/hooks/auth/use-secure-integration-secrets.test.tsx new file mode 100644 index 0000000..88ffeb9 --- /dev/null +++ b/client/src/hooks/auth/use-secure-integration-secrets.test.tsx @@ -0,0 +1,106 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { useSecureIntegrationSecrets } from '@/hooks/auth/use-secure-integration-secrets'; +import type { Integration } from '@/api/types'; + +let available = true; +const getSecureValue = vi.fn(); +const setSecureValue = vi.fn(); +const checkSecureStorageHealth = vi.fn(); +const migrateSecureStorage = vi.fn(); +const addClientLog = vi.fn(); + +vi.mock('@/api', () => ({ + isRecord: (value: unknown) => typeof value === 'object' && value !== null, +})); + +vi.mock('@/lib/storage/crypto', () => ({ + isSecureStorageAvailable: () => available, + getSecureValue: (key: string) => getSecureValue(key), + setSecureValue: (key: string, value: string) => setSecureValue(key, value), + checkSecureStorageHealth: () => checkSecureStorageHealth(), + migrateSecureStorage: () => migrateSecureStorage(), +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: (args: unknown) => addClientLog(args), +})); + +describe('useSecureIntegrationSecrets', () => { + const baseIntegration: Integration = { + id: 'int-1', + type: 'email', + name: 'Email', + enabled: true, + }; + + it('returns early when secure storage is unavailable', async () => { + available = false; + const { result } = renderHook(() => useSecureIntegrationSecrets()); + const { loadSecrets, saveSecrets, clearSecrets, loadAllSecrets, checkHealthAndMigrate } = + result.current; + + const loaded = await loadSecrets(baseIntegration); + expect(loaded).toBe(baseIntegration); + + await expect(saveSecrets(baseIntegration)).resolves.toBeUndefined(); + await expect(clearSecrets(baseIntegration)).resolves.toBeUndefined(); + await expect(loadAllSecrets([baseIntegration])).resolves.toEqual([baseIntegration]); + await expect(checkHealthAndMigrate()).resolves.toEqual({ + status: 'unavailable', + migrationState: 'not_needed', + }); + }); + + it('loads, saves, and clears secrets when available', async () => { + available = true; + getSecureValue.mockResolvedValueOnce('secret-1').mockRejectedValueOnce(new Error('nope')); + setSecureValue.mockResolvedValue(undefined); + + const { result } = renderHook(() => useSecureIntegrationSecrets()); + const { loadSecrets, saveSecrets, clearSecrets } = result.current; + + const loaded = await loadSecrets(baseIntegration); + expect(loaded).not.toBe(baseIntegration); + expect(loaded.email_config?.api_key).toBe('secret-1'); + expect(addClientLog).toHaveBeenCalled(); + + const withSecret: Integration = { + ...baseIntegration, + email_config: { api_key: 'value', smtp_password: undefined }, + }; + await saveSecrets(withSecret); + expect(setSecureValue).toHaveBeenCalledWith('integration_int-1_email_config_api_key', 'value'); + expect(setSecureValue).toHaveBeenCalledWith( + 'integration_int-1_email_config_smtp_password', + '' + ); + + setSecureValue.mockRejectedValueOnce(new Error('nope')); + await clearSecrets(baseIntegration); + expect(addClientLog).toHaveBeenCalled(); + }); + + it('checks health and runs migration paths', async () => { + available = true; + checkSecureStorageHealth.mockResolvedValueOnce('healthy'); + + const { result } = renderHook(() => useSecureIntegrationSecrets()); + const { checkHealthAndMigrate } = result.current; + const healthy = await checkHealthAndMigrate(); + expect(healthy).toEqual({ status: 'healthy', migrationState: 'not_needed' }); + + checkSecureStorageHealth + .mockResolvedValueOnce('failed') + .mockResolvedValueOnce('healthy') + .mockResolvedValueOnce('failed'); + migrateSecureStorage.mockResolvedValueOnce(true).mockResolvedValueOnce(false); + + const migrated = await checkHealthAndMigrate(); + expect(migrated.migrationState).toBe('succeeded'); + + const failed = await checkHealthAndMigrate(); + expect(failed.migrationState).toBe('failed'); + }); +}); diff --git a/client/src/hooks/data/use-async-data.test.tsx b/client/src/hooks/data/use-async-data.test.tsx new file mode 100644 index 0000000..54743aa --- /dev/null +++ b/client/src/hooks/data/use-async-data.test.tsx @@ -0,0 +1,117 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { useAsyncData, useMutation } from './use-async-data'; +import { extractErrorMessage } from '@/api'; + +vi.mock('@/api', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + extractErrorMessage: vi.fn(() => 'Boom'), + }; +}); + +describe('useAsyncData', () => { + it('fetches data and calls onSuccess', async () => { + const fetcher = vi.fn().mockResolvedValue('result'); + const onSuccess = vi.fn(); + + const { result } = renderHook(() => useAsyncData(fetcher, [], { onSuccess })); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.data).toBe('result'); + expect(onSuccess).toHaveBeenCalledWith('result'); + }); + + it('handles errors and calls onError', async () => { + const fetcher = vi.fn().mockRejectedValue(new Error('boom')); + const onError = vi.fn(); + + const { result } = renderHook(() => useAsyncData(fetcher, [], { onError })); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(extractErrorMessage).toHaveBeenCalled(); + expect(result.current.error).toBe('Boom'); + expect(onError).toHaveBeenCalledWith('Boom'); + }); + + it('skips initial fetch when requested', async () => { + const fetcher = vi.fn().mockResolvedValue('result'); + + const { result } = renderHook(() => useAsyncData(fetcher, [], { skip: true })); + + expect(result.current.isLoading).toBe(false); + expect(fetcher).not.toHaveBeenCalled(); + + result.current.refetch(); + expect(fetcher).not.toHaveBeenCalled(); + }); + + it('supports refetch and reset', async () => { + const fetcher = vi.fn().mockResolvedValue('first'); + const { result, rerender } = renderHook( + ({ initialData }) => useAsyncData(fetcher, [], { initialData }), + { initialProps: { initialData: 'initial' } } + ); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.data).toBe('first'); + + result.current.reset(); + + await waitFor(() => { + expect(result.current.data).toBe('initial'); + expect(result.current.error).toBeNull(); + }); + + rerender({ initialData: 'next' }); + result.current.refetch(); + + await waitFor(() => { + expect(fetcher).toHaveBeenCalledTimes(2); + }); + }); + + it('handles mutation success and errors', async () => { + const mutationFn = vi.fn().mockResolvedValue({ ok: true }); + const onSuccess = vi.fn(); + const onError = vi.fn(); + + const { result } = renderHook(() => useMutation(mutationFn, { onSuccess, onError })); + + const success = await result.current.mutate('payload'); + expect(success).toEqual({ ok: true }); + expect(onSuccess).toHaveBeenCalledWith({ ok: true }); + + mutationFn.mockRejectedValueOnce(new Error('boom')); + const failure = await result.current.mutate('payload'); + expect(failure).toBeUndefined(); + expect(onError).toHaveBeenCalledWith('Boom'); + }); + + it('resets mutation state', async () => { + const mutationFn = vi.fn().mockResolvedValue('ok'); + const { result } = renderHook(() => useMutation(mutationFn)); + + await result.current.mutate('payload'); + await waitFor(() => { + expect(result.current.data).toBe('ok'); + }); + + result.current.reset(); + await waitFor(() => { + expect(result.current.data).toBeUndefined(); + expect(result.current.error).toBeNull(); + expect(result.current.isLoading).toBe(false); + }); + }); +}); diff --git a/client/src/hooks/data/use-async-data.ts b/client/src/hooks/data/use-async-data.ts index 44845a4..0730a5b 100644 --- a/client/src/hooks/data/use-async-data.ts +++ b/client/src/hooks/data/use-async-data.ts @@ -107,8 +107,6 @@ export function useAsyncData( const onErrorRef = useRef(onError); onErrorRef.current = onError; - // The fetcher is intentionally excluded - deps are controlled by caller - // biome-ignore lint/correctness/useExhaustiveDependencies: fetcher is intentionally excluded, user controls deps const doFetch = useCallback(async () => { if (skipRef.current) { return; @@ -136,7 +134,7 @@ export function useAsyncData( onErrorRef.current?.(errorMessage); } } - }, deps); + }, [fetcher, ...deps]); const refetch = useCallback(() => { void doFetch(); diff --git a/client/src/hooks/data/use-project-members.test.tsx b/client/src/hooks/data/use-project-members.test.tsx new file mode 100644 index 0000000..e7746ee --- /dev/null +++ b/client/src/hooks/data/use-project-members.test.tsx @@ -0,0 +1,54 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { useProjectMembers } from '@/hooks/data/use-project-members'; + +const mockListProjectMembers = vi.fn(); + +vi.mock('@/api/interface', () => ({ + getAPI: () => ({ + listProjectMembers: mockListProjectMembers, + }), +})); + +vi.mock('@/api', () => ({ + extractErrorMessage: (_err: unknown, fallback?: string) => fallback ?? 'Error', +})); + +describe('useProjectMembers', () => { + it('does nothing when projectId is undefined', async () => { + const { result } = renderHook(() => useProjectMembers()); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(mockListProjectMembers).not.toHaveBeenCalled(); + }); + + it('loads members successfully', async () => { + mockListProjectMembers.mockResolvedValueOnce({ members: [{ id: 'm1', role: 'admin' }] }); + + const { result } = renderHook(() => useProjectMembers('project-1')); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.members).toHaveLength(1); + expect(result.current.error).toBeNull(); + }); + + it('handles errors with fallback message', async () => { + mockListProjectMembers.mockRejectedValueOnce(new Error('boom')); + + const { result } = renderHook(() => useProjectMembers('project-2')); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.members).toHaveLength(0); + expect(result.current.error).toBe('Failed to load project members'); + }); +}); diff --git a/client/src/hooks/data/use-project.test.tsx b/client/src/hooks/data/use-project.test.tsx new file mode 100644 index 0000000..577420a --- /dev/null +++ b/client/src/hooks/data/use-project.test.tsx @@ -0,0 +1,46 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { useActiveProject, useProject } from '@/hooks/data/use-project'; + +const mockUseProjects = vi.fn(); + +vi.mock('@/contexts/project-state', () => ({ + useProjects: () => mockUseProjects(), +})); + +describe('useProject', () => { + it('returns project by id or null when missing', () => { + mockUseProjects.mockReturnValue({ + projects: [ + { id: 'project-1', name: 'Alpha' }, + { id: 'project-2', name: 'Beta' }, + ], + }); + + const { result: found } = renderHook(() => useProject('project-2')); + expect(found.current?.name).toBe('Beta'); + + const { result: missing } = renderHook(() => useProject('missing')); + expect(missing.current).toBeNull(); + + const { result: none } = renderHook(() => useProject()); + expect(none.current).toBeNull(); + }); +}); + +describe('useActiveProject', () => { + it('exposes active project and setter', () => { + const switchProject = vi.fn(); + mockUseProjects.mockReturnValue({ + activeProject: { id: 'project-1', name: 'Alpha' }, + switchProject, + }); + + const { result } = renderHook(() => useActiveProject()); + expect(result.current.activeProject?.id).toBe('project-1'); + + result.current.setActiveProject('project-2'); + expect(switchProject).toHaveBeenCalledWith('project-2'); + }); +}); diff --git a/client/src/hooks/processing/events.test.tsx b/client/src/hooks/processing/events.test.tsx new file mode 100644 index 0000000..5127887 --- /dev/null +++ b/client/src/hooks/processing/events.test.tsx @@ -0,0 +1,98 @@ +import { act, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { usePostProcessingEvents } from '@/hooks/processing/events'; + +type PostProcessingEvent = { + meeting_id: string | null; + stage: 'started' | 'running' | 'completed' | 'failed'; + progress?: number; +}; + +const handlers = new Map void>(); + +vi.mock('@/lib/system/events', () => ({ + TauriEvents: { + SUMMARY_PROGRESS: 'summary_progress', + DIARIZATION_PROGRESS: 'diarization_progress', + }, + useTauriEvent: (eventName: string, handler: (event: PostProcessingEvent) => void) => { + handlers.set(eventName, handler); + }, +})); + +describe('usePostProcessingEvents', () => { + it('ignores events for different meeting id', () => { + const updateStepState = vi.fn(); + const stopPolling = vi.fn(); + + renderHook(() => + usePostProcessingEvents({ + meetingId: 'm1', + updateStepState, + stopPolling, + }) + ); + + act(() => { + handlers.get('summary_progress')?.({ meeting_id: 'other', stage: 'completed' }); + }); + + expect(updateStepState).not.toHaveBeenCalled(); + }); + + it('handles summary and diarization events', () => { + const updateStepState = vi.fn(); + const stopPolling = vi.fn(); + const onStepComplete = vi.fn(); + const onStepError = vi.fn(); + + renderHook(() => + usePostProcessingEvents({ + meetingId: 'm1', + updateStepState, + stopPolling, + onStepComplete, + onStepError, + }) + ); + + act(() => { + handlers.get('summary_progress')?.({ meeting_id: 'm1', stage: 'completed' }); + }); + expect(updateStepState).toHaveBeenCalledWith('summary', { + status: 'completed', + completedAt: expect.any(Number), + }); + + act(() => { + handlers.get('summary_progress')?.({ meeting_id: 'm1', stage: 'failed' }); + }); + expect(updateStepState).toHaveBeenCalledWith('summary', { + status: 'failed', + error: 'Summary generation failed', + completedAt: expect.any(Number), + }); + + act(() => { + handlers.get('diarization_progress')?.({ meeting_id: 'm1', stage: 'completed' }); + }); + expect(stopPolling).toHaveBeenCalled(); + expect(updateStepState).toHaveBeenCalledWith('diarization', { + status: 'completed', + completedAt: expect.any(Number), + }); + expect(onStepComplete).toHaveBeenCalledWith('diarization'); + + act(() => { + handlers.get('diarization_progress')?.({ meeting_id: 'm1', stage: 'failed' }); + }); + expect(stopPolling).toHaveBeenCalledTimes(2); + expect(updateStepState).toHaveBeenCalledWith('diarization', { + status: 'failed', + error: 'Diarization failed', + completedAt: expect.any(Number), + }); + expect(onStepError).toHaveBeenCalledWith('diarization', 'Diarization failed'); + }); +}); diff --git a/client/src/hooks/processing/use-entity-extraction.test.tsx b/client/src/hooks/processing/use-entity-extraction.test.tsx new file mode 100644 index 0000000..d4652c0 --- /dev/null +++ b/client/src/hooks/processing/use-entity-extraction.test.tsx @@ -0,0 +1,124 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useEntityExtraction } from '@/hooks/processing/use-entity-extraction'; + +const api = { + extractEntities: vi.fn(), +}; +const toast = vi.fn(); +const toastError = vi.fn((args: { fallback: string }) => args.fallback); +const getEntities = vi.fn(); +const setEntitiesFromExtraction = vi.fn(); +const clearEntities = vi.fn(); +let entitiesSubscriber: (() => void) | undefined; + +vi.mock('@/api/interface', () => ({ + getAPI: () => api, +})); + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: (args: unknown) => toast(args), +})); + +vi.mock('@/lib/observability/errors', () => ({ + toastError: (args: { fallback: string }) => toastError(args), +})); + +vi.mock('@/lib/state/entities', () => ({ + getEntities: () => getEntities(), + setEntitiesFromExtraction: (...args: unknown[]) => setEntitiesFromExtraction(...args), + clearEntities: () => clearEntities(), + subscribeToEntities: (handler: () => void) => { + entitiesSubscriber = handler; + return () => {}; + }, +})); + +describe('useEntityExtraction', () => { + beforeEach(() => { + api.extractEntities.mockReset(); + toast.mockReset(); + toastError.mockReset(); + getEntities.mockReset(); + setEntitiesFromExtraction.mockReset(); + clearEntities.mockReset(); + entitiesSubscriber = undefined; + getEntities.mockReturnValue([]); + }); + + it('initializes with store entities and handles updates', async () => { + getEntities.mockReturnValueOnce([{ id: 'e1' }]); + const { result } = renderHook(() => + useEntityExtraction({ meetingId: 'm1', meetingState: 'completed' }) + ); + + expect(result.current.state.entities).toEqual([{ id: 'e1' }]); + + getEntities.mockReturnValueOnce([{ id: 'e2' }]); + act(() => { + entitiesSubscriber?.(); + }); + expect(result.current.state.entities).toEqual([{ id: 'e2' }]); + }); + + it('handles missing meeting id', async () => { + const { result } = renderHook(() => useEntityExtraction({ meetingId: undefined })); + + await act(async () => { + await result.current.extract(); + }); + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('No meeting ID provided'); + }); + + it('handles extraction errors', async () => { + api.extractEntities.mockRejectedValueOnce(new Error('fail')); + const { result } = renderHook(() => useEntityExtraction({ meetingId: 'm1' })); + + await act(async () => { + await result.current.extract(true); + }); + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('Failed to extract entities'); + }); + + it('extracts entities and clears store', async () => { + api.extractEntities.mockResolvedValueOnce({ + entities: [{ id: 'e1' }], + cached: true, + }); + + const { result } = renderHook(() => + useEntityExtraction({ meetingId: 'm1', meetingTitle: 'Meeting', meetingState: 'completed' }) + ); + + await act(async () => { + await result.current.extract(); + }); + + await waitFor(() => { + expect(result.current.state.status).toBe('success'); + }); + expect(setEntitiesFromExtraction).toHaveBeenCalledWith([{ id: 'e1' }], 'Meeting'); + expect(toast).toHaveBeenCalled(); + + act(() => { + result.current.clearEntities(); + }); + expect(clearEntities).toHaveBeenCalled(); + expect(result.current.state.status).toBe('idle'); + }); + + it('auto-extracts when enabled and meeting is completed', async () => { + api.extractEntities.mockResolvedValueOnce({ entities: [], cached: false }); + + renderHook(() => + useEntityExtraction({ meetingId: 'm1', meetingState: 'completed', autoExtract: true }) + ); + + await waitFor(() => { + expect(api.extractEntities).toHaveBeenCalled(); + }); + }); +}); diff --git a/client/src/hooks/recording/use-recording-app-policy.test.tsx b/client/src/hooks/recording/use-recording-app-policy.test.tsx new file mode 100644 index 0000000..a861115 --- /dev/null +++ b/client/src/hooks/recording/use-recording-app-policy.test.tsx @@ -0,0 +1,224 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; +import type { + InstalledAppInfo, + RecordingAppPolicy, + RecordingAppRule, + UserPreferences, +} from '@/api/types'; +import { getAPI } from '@/api/interface'; +import { isTauriEnvironment } from '@/api'; +import { toastError } from '@/lib/observability/errors'; +import { preferences } from '@/lib/preferences'; +import { defaultPreferences } from '@/lib/preferences/constants'; +import { useRecordingAppPolicy } from './use-recording-app-policy'; + +vi.mock('@/api/interface', () => ({ + getAPI: vi.fn(), +})); + +vi.mock('@/api', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isTauriEnvironment: vi.fn(() => true), + }; +}); + +vi.mock('@/lib/observability/errors', () => ({ + toastError: vi.fn(), +})); + +vi.mock('@/lib/preferences', () => ({ + preferences: { + getRecordingAppPolicy: vi.fn(), + setRecordingAppPolicy: vi.fn(), + subscribe: vi.fn(), + }, +})); + +const buildApp = (overrides: Partial = {}): InstalledAppInfo => ({ + name: 'Zoom', + bundle_id: 'com.zoom.us', + app_id: null, + exe_path: null, + exe_name: null, + desktop_id: null, + is_pwa: false, + ...overrides, +}); + +const buildRule = (overrides: Partial = {}): RecordingAppRule => ({ + id: 'zoom', + label: 'Zoom', + source: 'detected', + matchers: [{ os: 'macos', kind: 'bundle_id', value: 'com.zoom.us' }], + ...overrides, +}); + +const buildResponse = (apps: InstalledAppInfo[], total: number, hasMore: boolean) => ({ + apps, + total, + has_more: hasMore, + page: 0, + page_size: 50, +}); + +describe('useRecordingAppPolicy', () => { + let policy: RecordingAppPolicy; + let listeners: Set<(prefs: UserPreferences) => void>; + + beforeEach(() => { + policy = { allowlist: [], denylist: [] }; + listeners = new Set(); + Object.defineProperty(window.navigator, 'platform', { + value: 'MacIntel', + configurable: true, + }); + + vi.mocked(preferences.getRecordingAppPolicy).mockImplementation(() => policy); + vi.mocked(preferences.setRecordingAppPolicy).mockImplementation((next) => { + policy = next; + const nextPrefs: UserPreferences = { ...defaultPreferences, recording_app_policy: policy }; + for (const listener of listeners) { + listener(nextPrefs); + } + }); + vi.mocked(preferences.subscribe).mockImplementation((listener) => { + listeners.add(listener); + const nextPrefs: UserPreferences = { ...defaultPreferences, recording_app_policy: policy }; + listener(nextPrefs); + return () => listeners.delete(listener); + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('loads common apps on mount in desktop mode', async () => { + const listInstalledApps = vi.fn().mockResolvedValue(buildResponse([buildApp()], 1, false)); + const api = { listInstalledApps }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isTauriEnvironment).mockReturnValue(true); + + const { result } = renderHook(() => useRecordingAppPolicy()); + + await waitFor(() => { + expect(result.current.installedApps).toHaveLength(1); + }); + + expect(listInstalledApps).toHaveBeenCalledWith({ + commonOnly: true, + page: 0, + pageSize: 50, + }); + expect(result.current.hasLoadedAll).toBe(false); + expect(result.current.getCanonicalId(buildApp())).toBe('zoom'); + }); + + it('appends unique apps when loading more pages', async () => { + const appA = buildApp({ name: 'Zoom' }); + const appB = buildApp({ name: 'Slack', bundle_id: 'com.slack' }); + const appC = buildApp({ name: 'Custom App', bundle_id: null, app_id: 'custom-app' }); + + const listInstalledApps = vi + .fn() + .mockResolvedValueOnce(buildResponse([appA], 1, true)) + .mockResolvedValueOnce(buildResponse([appA, appB], 3, true)) + .mockResolvedValueOnce(buildResponse([appB, appC], 3, false)); + + const api = { listInstalledApps }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isTauriEnvironment).mockReturnValue(true); + + const { result } = renderHook(() => useRecordingAppPolicy()); + + await waitFor(() => { + expect(result.current.installedApps).toHaveLength(1); + }); + + await act(async () => { + await result.current.loadAllApps(); + }); + + await act(async () => { + await result.current.loadMoreApps(); + }); + + expect(result.current.installedApps.map((app) => app.name)).toEqual([ + 'Zoom', + 'Slack', + 'Custom App', + ]); + expect(result.current.hasLoadedAll).toBe(true); + }); + + it('moves rules between lists based on priority', async () => { + policy = { + allowlist: [], + denylist: [buildRule()], + }; + + const listInstalledApps = vi.fn().mockResolvedValue(buildResponse([], 0, false)); + const api = { listInstalledApps }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isTauriEnvironment).mockReturnValue(true); + + const { result } = renderHook(() => useRecordingAppPolicy()); + + await waitFor(() => { + expect(result.current.denylist).toHaveLength(1); + }); + + await act(async () => { + result.current.addAppToAllowlist(buildApp(), 'manual'); + }); + + expect(policy.allowlist).toHaveLength(1); + expect(policy.denylist).toHaveLength(0); + expect(policy.allowlist[0]?.source).toBe('manual'); + expect(policy.allowlist[0]?.matchers.length).toBeGreaterThan(0); + }); + + it('removes rules from the specified list', async () => { + policy = { + allowlist: [buildRule()], + denylist: [], + }; + + const listInstalledApps = vi.fn().mockResolvedValue(buildResponse([], 0, false)); + const api = { listInstalledApps }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isTauriEnvironment).mockReturnValue(true); + + const { result } = renderHook(() => useRecordingAppPolicy()); + + await waitFor(() => { + expect(result.current.allowlist).toHaveLength(1); + }); + + await act(async () => { + result.current.removeRule('allowlist', 'zoom'); + }); + + expect(policy.allowlist).toHaveLength(0); + }); + + it('reports load errors and clears loading state', async () => { + const listInstalledApps = vi.fn().mockRejectedValue(new Error('boom')); + const api = { listInstalledApps }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isTauriEnvironment).mockReturnValue(true); + + const { result } = renderHook(() => useRecordingAppPolicy()); + + await waitFor(() => { + expect(result.current.isLoadingApps).toBe(false); + }); + + expect(toastError).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Failed to load applications' }) + ); + }); +}); diff --git a/client/src/hooks/sync/use-calendar-sync.test.tsx b/client/src/hooks/sync/use-calendar-sync.test.tsx new file mode 100644 index 0000000..f0fa93e --- /dev/null +++ b/client/src/hooks/sync/use-calendar-sync.test.tsx @@ -0,0 +1,188 @@ +import { act, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import type { CalendarEvent } from '@/api/types'; +import { useCalendarSync } from './use-calendar-sync'; +import { toast } from '@/hooks/ui/use-toast'; +import { addClientLog } from '@/lib/observability/client'; +import { toastError, errorMessageFrom } from '@/lib/observability/errors'; +import { getAPI } from '@/api/interface'; +import { isIntegrationNotFoundError } from '@/api'; + +vi.mock('@/api/interface', () => ({ + getAPI: vi.fn(), +})); + +vi.mock('@/api', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isIntegrationNotFoundError: vi.fn(() => false), + }; +}); + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: vi.fn(), +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +vi.mock('@/lib/observability/errors', () => ({ + toastError: vi.fn(), + errorMessageFrom: vi.fn(() => 'Calendar error'), +})); + +const buildEvent = (id: string): CalendarEvent => ({ + id, + title: 'Sync', + start_time: 10, + end_time: 20, +}); + +describe('useCalendarSync', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.clearAllMocks(); + }); + + it('fetches providers and updates state', async () => { + const getCalendarProviders = vi.fn().mockResolvedValue({ providers: ['google'] }); + const api = { getCalendarProviders }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + + const { result } = renderHook(() => useCalendarSync()); + + await act(async () => { + await result.current.fetchProviders(); + }); + + expect(result.current.state.providers).toEqual(['google']); + }); + + it('logs provider fetch failures without throwing', async () => { + const getCalendarProviders = vi.fn().mockRejectedValue(new Error('boom')); + const api = { getCalendarProviders }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + + const { result } = renderHook(() => useCalendarSync()); + + await act(async () => { + await result.current.fetchProviders(); + }); + + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Calendar provider fetch failed (non-critical)' }) + ); + }); + + it('fetches events and updates state', async () => { + const listCalendarEvents = vi.fn().mockResolvedValue({ events: [buildEvent('event-1')] }); + const api = { listCalendarEvents }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + + const { result } = renderHook(() => useCalendarSync()); + + await act(async () => { + await result.current.fetchEvents(); + }); + + expect(result.current.state.status).toBe('success'); + expect(result.current.state.events).toHaveLength(1); + expect(result.current.state.lastSync).not.toBeNull(); + }); + + it('handles missing integrations with user toast', async () => { + const listCalendarEvents = vi.fn().mockRejectedValue(new Error('missing')); + const api = { listCalendarEvents }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isIntegrationNotFoundError).mockReturnValue(true); + + const { result } = renderHook(() => useCalendarSync()); + + await act(async () => { + await result.current.fetchEvents(); + }); + + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('Calendar integration no longer exists'); + expect(toast).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Calendar Disconnected' }) + ); + }); + + it('handles generic errors with toast and error message', async () => { + const listCalendarEvents = vi.fn().mockRejectedValue(new Error('boom')); + const api = { listCalendarEvents }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + vi.mocked(isIntegrationNotFoundError).mockReturnValue(false); + + const { result } = renderHook(() => useCalendarSync()); + + await act(async () => { + await result.current.fetchEvents(); + }); + + expect(errorMessageFrom).toHaveBeenCalled(); + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('Calendar error'); + expect(toastError).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Calendar Sync Failed' }) + ); + }); + + it('starts and stops auto refresh', async () => { + const listCalendarEvents = vi.fn().mockResolvedValue({ events: [] }); + const api = { listCalendarEvents }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + + const { result } = renderHook(() => useCalendarSync({ autoRefreshInterval: 1000 })); + + act(() => { + result.current.startAutoRefresh(); + }); + + expect(result.current.isAutoRefreshing).toBe(true); + + await act(async () => { + vi.advanceTimersByTime(1000); + }); + + expect(listCalendarEvents).toHaveBeenCalled(); + + act(() => { + result.current.stopAutoRefresh(); + }); + + expect(result.current.isAutoRefreshing).toBe(false); + }); + + it('prevents overlapping fetches', async () => { + let resolvePromise: ((value: { events: CalendarEvent[] }) => void) | null = null; + const listCalendarEvents = vi.fn( + () => + new Promise<{ events: CalendarEvent[] }>((resolve) => { + resolvePromise = resolve; + }) + ); + const api = { listCalendarEvents }; + vi.mocked(getAPI).mockReturnValue(api as ReturnType); + + const { result } = renderHook(() => useCalendarSync()); + + act(() => { + void result.current.fetchEvents(); + void result.current.fetchEvents(); + }); + + expect(listCalendarEvents).toHaveBeenCalledTimes(1); + + await act(async () => { + resolvePromise?.({ events: [] }); + }); + }); +}); diff --git a/client/src/hooks/sync/use-integration-validation.test.tsx b/client/src/hooks/sync/use-integration-validation.test.tsx new file mode 100644 index 0000000..85cffdb --- /dev/null +++ b/client/src/hooks/sync/use-integration-validation.test.tsx @@ -0,0 +1,122 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useIntegrationValidation } from '@/hooks/sync/use-integration-validation'; +import type { + IntegrationValidationEvent, + IntegrationValidationListener, +} from '@/lib/preferences/validation-events'; + +const mockToast = vi.fn(); +let validationHandler: IntegrationValidationListener | undefined; + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: (args: unknown) => mockToast(args), +})); + +vi.mock('@/lib/preferences', () => ({ + subscribeToValidationEvents: (handler: IntegrationValidationListener) => { + validationHandler = handler; + return () => {}; + }, +})); + +async function waitForHandler(previous?: IntegrationValidationListener) { + await waitFor(() => { + expect(validationHandler).toBeDefined(); + if (previous) { + expect(validationHandler).not.toBe(previous); + } + }); + if (!validationHandler) { + throw new Error('Validation handler was not registered'); + } + return validationHandler; +} + +describe('useIntegrationValidation', () => { + beforeEach(() => { + mockToast.mockClear(); + validationHandler = undefined; + }); + + it('updates state and shows toast on validation failure', async () => { + const { result } = renderHook(() => useIntegrationValidation()); + const handler = await waitForHandler(); + + act(() => { + const event: IntegrationValidationEvent = { + type: 'validation_failed', + error: 'bad', + timestamp: 123, + }; + handler(event); + }); + + await waitFor(() => { + expect(result.current.lastError).toBe('bad'); + expect(result.current.isValidating).toBe(false); + expect(result.current.lastEventTimestamp).toBe(123); + }); + expect(mockToast).toHaveBeenCalled(); + }); + + it('handles integrations removed and optional notifications', async () => { + const { result: withToast } = renderHook(() => useIntegrationValidation()); + const firstHandler = await waitForHandler(); + + act(() => { + const event: IntegrationValidationEvent = { + type: 'integrations_removed', + removedIntegrationIds: ['a'], + removedIntegrationNames: ['One'], + timestamp: 321, + }; + firstHandler(event); + }); + + await waitFor(() => { + expect(withToast.current.removedIntegrationIds).toEqual(['a']); + expect(withToast.current.removedIntegrationNames).toEqual(['One']); + }); + expect(mockToast).toHaveBeenCalled(); + + mockToast.mockClear(); + const { result: noToast } = renderHook(() => useIntegrationValidation({ showNotifications: false })); + const secondHandler = await waitForHandler(firstHandler); + + act(() => { + const event: IntegrationValidationEvent = { + type: 'integrations_removed', + removedIntegrationIds: ['b'], + removedIntegrationNames: ['Two'], + timestamp: 222, + }; + secondHandler(event); + }); + + await waitFor(() => { + expect(noToast.current.removedIntegrationNames).toEqual(['Two']); + }); + expect(mockToast).not.toHaveBeenCalled(); + }); + + it('clears state on validation complete', async () => { + const { result } = renderHook(() => useIntegrationValidation()); + const handler = await waitForHandler(); + + act(() => { + const event: IntegrationValidationEvent = { + type: 'validation_complete', + timestamp: 999, + }; + handler(event); + }); + + await waitFor(() => { + expect(result.current.lastError).toBeNull(); + expect(result.current.removedIntegrationIds).toEqual([]); + expect(result.current.lastEventTimestamp).toBe(999); + }); + }); +}); diff --git a/client/src/hooks/sync/use-meeting-reminders.test.tsx b/client/src/hooks/sync/use-meeting-reminders.test.tsx new file mode 100644 index 0000000..ac27269 --- /dev/null +++ b/client/src/hooks/sync/use-meeting-reminders.test.tsx @@ -0,0 +1,159 @@ +import { act, renderHook } from '@testing-library/react'; +import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; +import type { CalendarEvent } from '@/api/types'; +import { Timing } from '@/api'; +import { toast } from '@/hooks/ui/use-toast'; +import { addClientLog } from '@/lib/observability/client'; +import { useMeetingReminders } from './use-meeting-reminders'; + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: vi.fn(), +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +const buildEvent = (overrides: Partial = {}): CalendarEvent => ({ + id: 'event-1', + title: 'Planning Session', + start_time: Math.floor(Date.now() / 1000) + 15 * 60, + end_time: Math.floor(Date.now() / 1000) + 30 * 60, + meeting_link: 'https://example.com/meet', + ...overrides, +}); + +class MockNotification { + static permission: NotificationPermission = 'default'; + static requestPermission = vi.fn<[], Promise>(); + + title: string; + options?: NotificationOptions; + onclick: (() => void) | null = null; + close = vi.fn(); + + constructor(title: string, options?: NotificationOptions) { + this.title = title; + this.options = options; + MockNotification.instances.push(this); + } + + static instances: MockNotification[] = []; +} + +type NotificationPermission = 'default' | 'granted' | 'denied'; + +describe('useMeetingReminders', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-01-01T10:00:00Z')); + MockNotification.permission = 'default'; + MockNotification.requestPermission.mockReset(); + MockNotification.instances = []; + vi.stubGlobal('Notification', MockNotification as unknown as typeof Notification); + Object.defineProperty(window, 'focus', { value: vi.fn(), configurable: true }); + Object.defineProperty(window, 'open', { value: vi.fn(), configurable: true }); + localStorage.clear(); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); + vi.clearAllMocks(); + localStorage.clear(); + }); + + it('reports unsupported notifications when Notification API is missing', async () => { + const original = window.Notification; + delete (window as Window & { Notification?: unknown }).Notification; + + const { result } = renderHook(() => useMeetingReminders([])); + + const granted = await act(async () => result.current.requestPermission()); + + expect(granted).toBe(false); + expect(toast).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Notifications not supported' }) + ); + + (window as Window & { Notification?: unknown }).Notification = original; + }); + + it('handles permission request outcomes', async () => { + MockNotification.requestPermission.mockResolvedValueOnce('granted'); + + const { result } = renderHook(() => useMeetingReminders([])); + + const granted = await act(async () => result.current.requestPermission()); + + expect(granted).toBe(true); + expect(result.current.permission).toBe('granted'); + expect(toast).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Notifications enabled' }) + ); + + MockNotification.requestPermission.mockResolvedValueOnce('denied'); + + const denied = await act(async () => result.current.requestPermission()); + + expect(denied).toBe(false); + expect(toast).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Notifications blocked' }) + ); + }); + + it('logs permission request failures', async () => { + MockNotification.requestPermission.mockRejectedValueOnce(new Error('boom')); + + const { result } = renderHook(() => useMeetingReminders([])); + + const granted = await act(async () => result.current.requestPermission()); + + expect(granted).toBe(false); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Notification permission request failed' }) + ); + }); + + it('sends reminders and avoids duplicate notifications', async () => { + MockNotification.permission = 'granted'; + const event = buildEvent({ + start_time: Math.floor(Date.now() / 1000) + 15 * 60, + }); + + const { result } = renderHook(() => useMeetingReminders([event])); + + await act(async () => { + await Promise.resolve(); + }); + + expect(result.current.permission).toBe('granted'); + const initialCount = MockNotification.instances.length; + expect(initialCount).toBeGreaterThan(0); + + act(() => { + vi.advanceTimersByTime(Timing.THIRTY_SECONDS_MS); + }); + expect(toast).toHaveBeenCalledWith( + expect.objectContaining({ title: 'Meeting in 15 minutes' }) + ); + }); + + it('toggles reminder settings and persists updates', async () => { + MockNotification.requestPermission.mockResolvedValueOnce('granted'); + + const { result } = renderHook(() => useMeetingReminders([])); + + await act(async () => { + await result.current.toggleReminders(); + }); + + expect(result.current.settings.enabled).toBe(false); + + await act(async () => { + await result.current.toggleReminders(); + }); + + expect(result.current.settings.enabled).toBe(true); + }); +}); diff --git a/client/src/hooks/sync/use-preferences-sync.test.tsx b/client/src/hooks/sync/use-preferences-sync.test.tsx new file mode 100644 index 0000000..21af952 --- /dev/null +++ b/client/src/hooks/sync/use-preferences-sync.test.tsx @@ -0,0 +1,156 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +async function setup() { + vi.resetModules(); + + const state = { + currentMeta: { + status: 'idle', + etag: null, + serverUpdatedAt: null, + lastSyncedAt: null, + error: null as string | null, + conflictPreferences: null, + conflictMessage: null, + }, + preferencesSubscriber: undefined as undefined | (() => void), + connectionSubscriber: undefined as + | undefined + | ((state: { mode: string; error?: string | null }) => void), + connectionState: { mode: 'connected', error: null as string | null }, + }; + + const syncMocks = { + hydrateFromServer: vi.fn().mockResolvedValue(undefined), + pushToServer: vi.fn().mockResolvedValue(undefined), + resolvePreferencesConflict: vi.fn().mockResolvedValue(undefined), + getPreferencesSyncMeta: vi.fn(() => state.currentMeta), + shouldSuppressNextPreferencesPush: vi.fn(), + subscribePreferencesSync: vi.fn(), + addClientLog: vi.fn(), + }; + + const preferences = { + subscribe: vi.fn((handler: () => void) => { + state.preferencesSubscriber = handler; + return () => {}; + }), + }; + + vi.doMock('@/lib/preferences/sync', () => ({ + getPreferencesSyncMeta: () => syncMocks.getPreferencesSyncMeta(), + hydrateFromServer: () => syncMocks.hydrateFromServer(), + pushToServer: () => syncMocks.pushToServer(), + resolvePreferencesConflict: (strategy: 'use_server' | 'use_local') => + syncMocks.resolvePreferencesConflict(strategy), + shouldSuppressNextPreferencesPush: () => syncMocks.shouldSuppressNextPreferencesPush(), + subscribePreferencesSync: (handler: (meta: unknown) => void) => { + syncMocks.subscribePreferencesSync(handler); + handler(syncMocks.getPreferencesSyncMeta()); + return () => {}; + }, + })); + + vi.doMock('@/lib/preferences', () => ({ + preferences, + })); + + vi.doMock('@/api', () => ({ + getConnectionState: () => state.connectionState, + subscribeConnectionState: ( + handler: (stateArg: { mode: string; error?: string | null }) => void + ) => { + state.connectionSubscriber = handler; + return () => {}; + }, + })); + + vi.doMock('@/lib/observability/client', () => ({ + addClientLog: (args: unknown) => syncMocks.addClientLog(args), + })); + + const { usePreferencesSync } = await import('@/hooks/sync/use-preferences-sync'); + return { usePreferencesSync, state, syncMocks, preferences }; +} + +describe('usePreferencesSync', () => { + it('skips sync effects when passive', async () => { + const { usePreferencesSync, syncMocks, preferences, state } = await setup(); + renderHook(() => usePreferencesSync({ passive: true })); + + expect(syncMocks.subscribePreferencesSync).toHaveBeenCalled(); + expect(syncMocks.hydrateFromServer).not.toHaveBeenCalled(); + expect(preferences.subscribe).not.toHaveBeenCalled(); + expect(state.connectionSubscriber).toBeUndefined(); + }); + + it('hydrates on mount and reconnection', async () => { + const { usePreferencesSync, syncMocks, state } = await setup(); + syncMocks.hydrateFromServer + .mockRejectedValueOnce(new Error('fail')) + .mockResolvedValueOnce(undefined); + + renderHook(() => usePreferencesSync()); + await waitFor(() => { + expect(syncMocks.hydrateFromServer).toHaveBeenCalled(); + }); + expect(syncMocks.addClientLog).toHaveBeenCalled(); + + act(() => { + state.connectionSubscriber?.({ mode: 'connected' }); + }); + await waitFor(() => { + expect(syncMocks.hydrateFromServer).toHaveBeenCalledTimes(2); + }); + }); + + it('handles preference change branches and debounced push', async () => { + const { usePreferencesSync, syncMocks, state } = await setup(); + vi.useFakeTimers(); + syncMocks.shouldSuppressNextPreferencesPush.mockReturnValueOnce(true).mockReturnValueOnce(false); + + renderHook(() => usePreferencesSync()); + + act(() => { + state.preferencesSubscriber?.(); + }); + expect(syncMocks.pushToServer).not.toHaveBeenCalled(); + + state.currentMeta = { ...state.currentMeta, status: 'conflict' }; + act(() => { + state.preferencesSubscriber?.(); + }); + expect(syncMocks.pushToServer).not.toHaveBeenCalled(); + + state.connectionState = { mode: 'disconnected', error: 'offline' }; + state.currentMeta = { ...state.currentMeta, status: 'idle' }; + act(() => { + state.preferencesSubscriber?.(); + }); + + state.connectionState = { mode: 'connected', error: null }; + act(() => { + state.preferencesSubscriber?.(); + }); + await act(async () => { + vi.advanceTimersByTime(1200); + }); + expect(syncMocks.pushToServer).toHaveBeenCalled(); + vi.useRealTimers(); + }); + + it('exposes sync helpers', async () => { + const { usePreferencesSync, syncMocks } = await setup(); + const { result } = renderHook(() => usePreferencesSync()); + await act(async () => { + await result.current.syncNow(); + await result.current.hydrate(); + await result.current.resolveConflict('use_server'); + }); + + expect(syncMocks.pushToServer).toHaveBeenCalled(); + expect(syncMocks.hydrateFromServer).toHaveBeenCalled(); + expect(syncMocks.resolvePreferencesConflict).toHaveBeenCalledWith('use_server'); + }); +}); diff --git a/client/src/hooks/sync/use-webhooks.test.tsx b/client/src/hooks/sync/use-webhooks.test.tsx new file mode 100644 index 0000000..64ec7be --- /dev/null +++ b/client/src/hooks/sync/use-webhooks.test.tsx @@ -0,0 +1,126 @@ +import { act, renderHook } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useWebhooks } from '@/hooks/sync/use-webhooks'; + +const api = { + listWebhooks: vi.fn(), + registerWebhook: vi.fn(), + updateWebhook: vi.fn(), + deleteWebhook: vi.fn(), + getWebhookDeliveries: vi.fn(), +}; +const toast = vi.fn(); +const toastError = vi.fn((args: { fallback: string }) => args.fallback); + +vi.mock('@/api/interface', () => ({ + getAPI: () => api, +})); + +vi.mock('@/hooks/ui/use-toast', () => ({ + toast: (args: unknown) => toast(args), +})); + +vi.mock('@/lib/observability/errors', () => ({ + toastError: (args: { fallback: string }) => toastError(args), +})); + +describe('useWebhooks', () => { + beforeEach(() => { + api.listWebhooks.mockReset(); + api.registerWebhook.mockReset(); + api.updateWebhook.mockReset(); + api.deleteWebhook.mockReset(); + api.getWebhookDeliveries.mockReset(); + toast.mockReset(); + toastError.mockReset(); + }); + + it('fetches webhooks and handles errors', async () => { + api.listWebhooks.mockResolvedValueOnce({ webhooks: [{ id: 'w1', name: 'Hook' }] }); + const { result } = renderHook(() => useWebhooks()); + + await act(async () => { + await result.current.fetchWebhooks(); + }); + + expect(result.current.state.status).toBe('success'); + expect(result.current.state.webhooks).toHaveLength(1); + + api.listWebhooks.mockRejectedValueOnce(new Error('fail')); + await act(async () => { + await result.current.fetchWebhooks(true); + }); + expect(result.current.state.status).toBe('error'); + expect(result.current.state.error).toBe('Failed to fetch webhooks'); + }); + + it('creates, updates, and deletes webhooks', async () => { + api.registerWebhook.mockResolvedValueOnce({ id: 'w1', name: 'Hook' }); + api.updateWebhook.mockResolvedValueOnce({ id: 'w1', name: 'Updated' }); + api.deleteWebhook.mockResolvedValueOnce({ success: true }); + + const { result } = renderHook(() => useWebhooks()); + + let created = null; + await act(async () => { + created = await result.current.createWebhook({ + workspaceId: 'w1', + url: 'https://example.com', + events: ['meeting.completed'], + }); + }); + expect(created).toEqual({ id: 'w1', name: 'Hook' }); + expect(toast).toHaveBeenCalled(); + + let updated = null; + await act(async () => { + updated = await result.current.updateWebhook('w1', { name: 'Updated' }); + }); + expect(updated).toEqual({ id: 'w1', name: 'Updated' }); + expect(result.current.state.webhooks[0]?.name).toBe('Updated'); + + let deleted = false; + await act(async () => { + deleted = await result.current.deleteWebhook('w1'); + }); + expect(deleted).toBe(true); + expect(result.current.state.webhooks).toHaveLength(0); + }); + + it('handles webhook operation failures and deliveries', async () => { + api.registerWebhook.mockRejectedValueOnce(new Error('bad')); + api.updateWebhook.mockRejectedValueOnce(new Error('bad')); + api.deleteWebhook.mockResolvedValueOnce({ success: false }); + api.getWebhookDeliveries.mockResolvedValueOnce({ deliveries: [{ id: 'd1' }] }); + api.getWebhookDeliveries.mockRejectedValueOnce(new Error('bad')); + + const { result } = renderHook(() => useWebhooks()); + + await act(async () => { + const created = await result.current.createWebhook({ + workspaceId: 'w1', + url: 'https://example.com', + events: ['meeting.completed'], + }); + expect(created).toBeNull(); + }); + + await act(async () => { + const updated = await result.current.updateWebhook('w1', { name: 'x' }); + expect(updated).toBeNull(); + }); + + let deleted = true; + await act(async () => { + deleted = await result.current.deleteWebhook('w1'); + }); + expect(deleted).toBe(false); + + const deliveries = await result.current.fetchDeliveries('w1', 10); + expect(deliveries).toEqual([{ id: 'd1' }]); + + const empty = await result.current.fetchDeliveries('w1'); + expect(empty).toEqual([]); + }); +}); diff --git a/client/src/hooks/ui/use-animated-words.test.ts b/client/src/hooks/ui/use-animated-words.test.ts new file mode 100644 index 0000000..f0218e8 --- /dev/null +++ b/client/src/hooks/ui/use-animated-words.test.ts @@ -0,0 +1,38 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { useAnimatedWords } from '@/hooks/ui/use-animated-words'; + +describe('useAnimatedWords', () => { + it('returns empty array for empty text', () => { + const { result } = renderHook(() => useAnimatedWords('', { blockId: 'a' })); + expect(result.current).toEqual([]); + }); + + it('marks new words for animation and preserves seen words', () => { + const { result, rerender } = renderHook( + ({ text, blockId }) => useAnimatedWords(text, { blockId, staggerDelay: 50 }), + { initialProps: { text: 'hello world', blockId: 'block-1' } } + ); + + expect(result.current.length).toBe(2); + expect(result.current[0].shouldAnimate).toBe(true); + expect(result.current[1].shouldAnimate).toBe(true); + + rerender({ text: 'hello world again', blockId: 'block-1' }); + expect(result.current[0].shouldAnimate).toBe(false); + expect(result.current[1].shouldAnimate).toBe(false); + expect(result.current[2].shouldAnimate).toBe(true); + }); + + it('resets animation when blockId changes', () => { + const { result, rerender } = renderHook( + ({ text, blockId }) => useAnimatedWords(text, { blockId }), + { initialProps: { text: 'one two', blockId: 'block-1' } } + ); + + rerender({ text: 'one two', blockId: 'block-2' }); + expect(result.current[0].shouldAnimate).toBe(true); + expect(result.current[1].shouldAnimate).toBe(true); + }); +}); diff --git a/client/src/hooks/ui/use-mobile.test.tsx b/client/src/hooks/ui/use-mobile.test.tsx new file mode 100644 index 0000000..28634c8 --- /dev/null +++ b/client/src/hooks/ui/use-mobile.test.tsx @@ -0,0 +1,49 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { Breakpoints } from '@/lib/config'; +import { useIsMobile } from '@/hooks/ui/use-mobile'; + +const listeners = new Set<(ev: MediaQueryListEvent) => void>(); + +beforeEach(() => { + vi.stubGlobal('matchMedia', (query: string) => ({ + matches: false, + media: query, + addEventListener: (_: string, cb: (ev: MediaQueryListEvent) => void) => listeners.add(cb), + removeEventListener: (_: string, cb: (ev: MediaQueryListEvent) => void) => listeners.delete(cb), + onchange: null, + dispatchEvent: () => false, + })); +}); + +afterEach(() => { + listeners.clear(); + vi.unstubAllGlobals(); +}); + +describe('useIsMobile', () => { + it('returns true when width is below breakpoint', async () => { + window.innerWidth = Breakpoints.MOBILE - 1; + const { result } = renderHook(() => useIsMobile()); + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it('updates when matchMedia change fires', async () => { + window.innerWidth = Breakpoints.MOBILE + 10; + const { result } = renderHook(() => useIsMobile()); + await waitFor(() => { + expect(result.current).toBe(false); + }); + + window.innerWidth = Breakpoints.MOBILE - 10; + listeners.forEach((cb) => { + cb(new Event('change') as MediaQueryListEvent); + }); + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); +}); diff --git a/client/src/hooks/ui/use-recording-panels.test.tsx b/client/src/hooks/ui/use-recording-panels.test.tsx new file mode 100644 index 0000000..06f192f --- /dev/null +++ b/client/src/hooks/ui/use-recording-panels.test.tsx @@ -0,0 +1,87 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { useRecordingPanels } from './use-recording-panels'; + +describe('useRecordingPanels', () => { + it('toggles panel state when no ref is available', () => { + const setShowNotesPanel = vi.fn(); + const setShowStatsPanel = vi.fn(); + + const { result } = renderHook(() => + useRecordingPanels({ + showNotesPanel: true, + showStatsPanel: false, + notesPanelSize: 30, + statsPanelSize: 40, + setShowNotesPanel, + setShowStatsPanel, + }) + ); + + result.current.handleToggleNotesPanel(); + result.current.handleToggleStatsPanel(); + + expect(setShowNotesPanel).toHaveBeenCalledWith(false); + expect(setShowStatsPanel).toHaveBeenCalledWith(true); + }); + + it('delegates toggle to panel handle when ref is available', () => { + const setShowNotesPanel = vi.fn(); + const setShowStatsPanel = vi.fn(); + + const { result, rerender } = renderHook( + ({ showNotesPanel, showStatsPanel }) => + useRecordingPanels({ + showNotesPanel, + showStatsPanel, + notesPanelSize: 25, + statsPanelSize: 35, + setShowNotesPanel, + setShowStatsPanel, + }), + { + initialProps: { showNotesPanel: true, showStatsPanel: false }, + } + ); + + const collapse = vi.fn(); + const expand = vi.fn(); + result.current.notesPanelRef.current = { collapse, expand } as unknown as { + collapse: () => void; + expand: (size: number) => void; + }; + + result.current.handleToggleNotesPanel(); + expect(collapse).toHaveBeenCalled(); + + rerender({ showNotesPanel: false, showStatsPanel: false }); + result.current.handleToggleNotesPanel(); + expect(expand).toHaveBeenCalledWith(25); + }); + + it('updates visibility on collapse and expand handlers', () => { + const setShowNotesPanel = vi.fn(); + const setShowStatsPanel = vi.fn(); + + const { result } = renderHook(() => + useRecordingPanels({ + showNotesPanel: true, + showStatsPanel: true, + notesPanelSize: 20, + statsPanelSize: 20, + setShowNotesPanel, + setShowStatsPanel, + }) + ); + + result.current.handleNotesCollapse(); + result.current.handleStatsCollapse(); + result.current.handleNotesExpand(); + result.current.handleStatsExpand(); + + expect(setShowNotesPanel).toHaveBeenCalledWith(false); + expect(setShowStatsPanel).toHaveBeenCalledWith(false); + expect(setShowNotesPanel).toHaveBeenCalledWith(true); + expect(setShowStatsPanel).toHaveBeenCalledWith(true); + }); +}); diff --git a/client/src/lib/ai-providers/strategies/custom.test.ts b/client/src/lib/ai-providers/strategies/custom.test.ts new file mode 100644 index 0000000..5139e06 --- /dev/null +++ b/client/src/lib/ai-providers/strategies/custom.test.ts @@ -0,0 +1,86 @@ +import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; +import { CustomStrategy } from './custom'; + +describe('CustomStrategy', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('fetches and filters models', async () => { + const response = { + ok: true, + json: vi.fn().mockResolvedValue({ + data: [{ id: 'gpt-4' }, { id: 'embedding-1' }, { id: 'gpt-4' }], + }), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new CustomStrategy(); + const result = await strategy.fetchModels('https://api.example.com', '', 'summary'); + + expect(result.success).toBe(true); + expect(result.models.map((model) => model.id)).toEqual(['gpt-4']); + }); + + it('handles non-ok responses', async () => { + const response = { + ok: false, + status: 500, + json: vi.fn().mockResolvedValue({ error: { message: 'bad' } }), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new CustomStrategy(); + const result = await strategy.fetchModels('https://api.example.com', 'key', 'summary'); + + expect(result.success).toBe(false); + expect(result.error).toBe('bad'); + }); + + it('tests transcription endpoints', async () => { + const response = { + ok: true, + status: 200, + json: vi.fn().mockResolvedValue({}), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new CustomStrategy(); + const result = await strategy.testEndpoint( + 'https://api.example.com', + '', + 'model', + 'transcription', + 0 + ); + + expect(result.success).toBe(true); + expect(result.message).toContain('Endpoint is responding'); + }); + + it('tests chat endpoints and reports failures', async () => { + const response = { + ok: false, + status: 401, + json: vi.fn().mockResolvedValue({ error: { message: 'unauthorized' } }), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new CustomStrategy(); + const result = await strategy.testEndpoint( + 'https://api.example.com', + 'key', + 'model', + 'summary', + 0 + ); + + expect(result.success).toBe(false); + expect(result.message).toBe('unauthorized'); + }); +}); diff --git a/client/src/lib/ai-providers/strategies/google.test.ts b/client/src/lib/ai-providers/strategies/google.test.ts new file mode 100644 index 0000000..a96769f --- /dev/null +++ b/client/src/lib/ai-providers/strategies/google.test.ts @@ -0,0 +1,79 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { GoogleStrategy } from './google'; + +describe('GoogleStrategy', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('fetches and normalizes models', async () => { + const response = { + ok: true, + json: vi.fn().mockResolvedValue({ + models: [{ name: 'models/gemini-1' }, { name: 'models/embedding-001' }], + }), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new GoogleStrategy(); + const result = await strategy.fetchModels('https://api.example.com', 'key', 'summary'); + + expect(result.success).toBe(true); + expect(result.models.map((model) => model.id)).toEqual(['embedding-001', 'gemini-1']); + }); + + it('handles non-ok responses', async () => { + const response = { + ok: false, + status: 500, + json: vi.fn().mockResolvedValue({}), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValue(response as Response); + + const strategy = new GoogleStrategy(); + const result = await strategy.fetchModels('https://api.example.com', 'key', 'summary'); + + expect(result.success).toBe(false); + expect(result.error).toBe('HTTP 500'); + }); + + it('tests endpoint success and failure', async () => { + const successResponse = { + ok: true, + json: vi.fn().mockResolvedValue({}), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValueOnce(successResponse as Response); + + const strategy = new GoogleStrategy(); + const success = await strategy.testEndpoint( + 'https://api.example.com', + 'key', + 'gemini-1', + 'summary', + 0 + ); + expect(success.success).toBe(true); + + const failureResponse = { + ok: false, + status: 400, + json: vi.fn().mockResolvedValue({ error: { message: 'bad' } }), + } satisfies Partial; + vi.mocked(fetch).mockResolvedValueOnce(failureResponse as Response); + + const failure = await strategy.testEndpoint( + 'https://api.example.com', + 'key', + 'gemini-1', + 'summary', + 0 + ); + expect(failure.success).toBe(false); + expect(failure.message).toBe('bad'); + }); +}); diff --git a/client/src/lib/constants/timing.ts b/client/src/lib/constants/timing.ts index b864f04..5eb60ea 100644 --- a/client/src/lib/constants/timing.ts +++ b/client/src/lib/constants/timing.ts @@ -99,7 +99,7 @@ export const GLOBAL_SPEAKER_ID = '__global__' as const; // ============================================================================= /** Default number of recent meetings to show on home page */ -export const HOME_RECENT_MEETINGS_LIMIT = 5; +export const HOME_RECENT_MEETINGS_LIMIT = 3; /** Default number of tasks to show on home page */ export const HOME_TASKS_LIMIT = 5; diff --git a/client/src/lib/integrations/oauth.test.ts b/client/src/lib/integrations/oauth.test.ts new file mode 100644 index 0000000..bcc1e22 --- /dev/null +++ b/client/src/lib/integrations/oauth.test.ts @@ -0,0 +1,113 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null); + +async function loadOAuthModule(options: { + isTauri: boolean; + deepLinkModule?: Record; + shellModule?: Record; +}) { + vi.resetModules(); + vi.doMock('@/api', () => ({ + isTauriEnvironment: () => options.isTauri, + })); + + if (options.deepLinkModule) { + vi.doMock('@tauri-apps/plugin-deep-link', () => options.deepLinkModule); + } + + if (options.shellModule) { + vi.doMock('@tauri-apps/plugin-shell', () => options.shellModule); + } + + return await import('@/lib/integrations/oauth'); +} + +describe('oauth utilities', () => { + afterEach(() => { + vi.unstubAllGlobals(); + openSpy.mockClear(); + vi.clearAllMocks(); + }); + + it('extracts callback params and validates state', async () => { + const { extractOAuthCallback, validateOAuthState } = await loadOAuthModule({ isTauri: false }); + + expect(extractOAuthCallback('https://example.com')).toBeNull(); + expect(extractOAuthCallback('noteflow://other?code=1&state=2')).toBeNull(); + + const result = extractOAuthCallback('noteflow://callback?code=abc&state=xyz'); + expect(result).toEqual({ code: 'abc', state: 'xyz' }); + + expect(validateOAuthState('xyz', 'xyz')).toBe(true); + expect(validateOAuthState('xyz', 'nope')).toBe(false); + expect(validateOAuthState('xyz', null)).toBe(false); + }); + + it('sets up deep link listener in tauri environment', async () => { + const cleanup = vi.fn(); + const deepLinkModule = { + onOpenUrl: vi.fn().mockResolvedValue(cleanup), + }; + const { setupDeepLinkListener } = await loadOAuthModule({ + isTauri: true, + deepLinkModule, + }); + + const handler = vi.fn(); + const result = await setupDeepLinkListener(handler); + + expect(deepLinkModule.onOpenUrl).toHaveBeenCalledWith(handler); + expect(result).toBe(cleanup); + }); + + it('skips deep link listener outside tauri', async () => { + const { setupDeepLinkListener } = await loadOAuthModule({ isTauri: false }); + const handler = vi.fn(); + await expect(setupDeepLinkListener(handler)).resolves.toBeUndefined(); + }); + + it('opens auth URL in browser when not tauri', async () => { + const { openAuthUrl } = await loadOAuthModule({ isTauri: false }); + const result = await openAuthUrl('https://auth'); + + expect(result).toBe(true); + expect(openSpy).toHaveBeenCalledWith('https://auth', '_blank'); + }); + + it('opens auth URL via tauri shell with fallback', async () => { + const shellModule = { + open: vi.fn().mockResolvedValue(undefined), + }; + const { openAuthUrl } = await loadOAuthModule({ isTauri: true, shellModule }); + + const result = await openAuthUrl('https://auth'); + expect(result).toBe(true); + expect(shellModule.open).toHaveBeenCalledWith('https://auth'); + + const errorShell = { + open: vi.fn().mockRejectedValue(new Error('boom')), + }; + const { openAuthUrl: openWithFallback } = await loadOAuthModule({ + isTauri: true, + shellModule: errorShell, + }); + await openWithFallback('https://auth2'); + expect(openSpy).toHaveBeenCalledWith('https://auth2', '_blank'); + }); + + it('generates deterministic oauth state when crypto is stubbed', async () => { + const { generateOAuthState } = await loadOAuthModule({ isTauri: false }); + vi.stubGlobal('crypto', { + getRandomValues: (arr: Uint8Array) => { + for (let i = 0; i < arr.length; i += 1) { + arr[i] = i; + } + return arr; + }, + }); + + const state = generateOAuthState(); + expect(state).toBe('000102030405060708090a0b0c0d0e0f'); + }); +}); diff --git a/client/src/lib/preferences/api.test.ts b/client/src/lib/preferences/api.test.ts new file mode 100644 index 0000000..8ea4ed4 --- /dev/null +++ b/client/src/lib/preferences/api.test.ts @@ -0,0 +1,199 @@ +import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; +import type { RecordingAppPolicy, RecordingAppRule, SyncHistoryEvent } from '@/api/types'; +import { _resetPreferencesForTesting } from './storage'; +import { addClientLog } from '@/lib/observability/client'; +import { generateId } from '@/api/adapters/mock/data'; + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +vi.mock('@/lib/observability/debug', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + debug: () => () => undefined, + }; +}); + +vi.mock('@/api/adapters/mock/data', () => ({ + generateId: vi.fn(() => 'custom-id'), +})); + +vi.mock('./tauri', async () => { + const { defaultPreferences } = await import('./constants'); + return { + hydratePreferencesFromTauri: vi.fn(async (handler: (prefs: unknown) => void) => { + handler(defaultPreferences); + }), + validateCachedIntegrations: vi.fn(async () => undefined), + persistPreferencesToTauri: vi.fn(async () => undefined), + isHydrated: vi.fn(() => true), + waitForHydration: vi.fn(() => Promise.resolve()), + }; +}); + +const resetPreferences = () => { + localStorage.clear(); + _resetPreferencesForTesting(); +}; + +const sampleRule: RecordingAppRule = { + id: 'zoom', + label: 'Zoom', + source: 'manual', + matchers: [{ os: 'macos', kind: 'bundle_id', value: 'com.zoom.us' }], +}; + +describe('preferences api', () => { + let preferences: typeof import('./api').preferences; + let hydratePreferencesFromTauri: typeof import('./tauri').hydratePreferencesFromTauri; + let validateCachedIntegrations: typeof import('./tauri').validateCachedIntegrations; + + beforeEach(async () => { + resetPreferences(); + vi.resetModules(); + const tauriModule = await import('./tauri'); + hydratePreferencesFromTauri = tauriModule.hydratePreferencesFromTauri; + validateCachedIntegrations = tauriModule.validateCachedIntegrations; + preferences = (await import('./api')).preferences; + }); + + afterEach(() => { + resetPreferences(); + vi.clearAllMocks(); + }); + + it('initializes and revalidates integrations', async () => { + await preferences.initialize(); + await preferences.revalidateIntegrations(); + + expect(hydratePreferencesFromTauri).toHaveBeenCalled(); + expect(validateCachedIntegrations).toHaveBeenCalledTimes(2); + }); + + it('builds server urls when customized', () => { + preferences.resetToDefaults(); + expect(preferences.getServerUrl()).toBe(''); + + preferences.setServerConnection('localhost', '5000'); + expect(preferences.getServerUrl()).toBe('http://localhost:5000'); + + preferences.setServerConnection('host-only', ''); + expect(preferences.getServerUrl()).toBe(''); + }); + + it('manages audio device preferences', () => { + preferences.setAudioDevice('input', 'mic-1', 'Mic'); + preferences.setSystemAudioDevice('loopback'); + preferences.setDualCaptureEnabled(true); + preferences.setAudioMixLevels(0.5, 0.7); + + const beforeReset = preferences.get().audio_devices; + expect(beforeReset.input_device_id).toBe('mic-1'); + expect(beforeReset.system_device_id).toBe('loopback'); + expect(beforeReset.dual_capture_enabled).toBe(true); + expect(beforeReset.mic_gain).toBe(0.5); + + preferences.resetAudioDevices(); + + const audioDevices = preferences.get().audio_devices; + expect(audioDevices.input_device_id).toBe(''); + expect(audioDevices.output_device_id).toBe(''); + expect(audioDevices.dual_capture_enabled).toBe(false); + expect(audioDevices.mic_gain).toBe(1); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Audio device preferences reset' }) + ); + }); + + it('updates AI config and templates', () => { + preferences.updateAIConfig('summary', { provider: 'openai', api_key: 'key' }, { + resetTestStatus: true, + }); + const summaryConfig = preferences.get().ai_config.summary; + expect(summaryConfig.provider).toBe('openai'); + expect(summaryConfig.test_status).toBe('untested'); + + preferences.setAIConfigTestStatus('summary', 'success'); + const updatedConfig = preferences.get().ai_config.summary; + expect(updatedConfig.test_status).toBe('success'); + expect(updatedConfig.last_tested).not.toBeNull(); + + preferences.setAITemplate('tone', 'casual'); + expect(preferences.get().ai_template.tone).toBe('casual'); + }); + + it('manages recording app policies', () => { + const policy: RecordingAppPolicy = { allowlist: [sampleRule], denylist: [] }; + preferences.setRecordingAppPolicy(policy); + expect(preferences.getRecordingAppPolicy().allowlist).toHaveLength(1); + + preferences.addRecordingAppRule('denylist', { ...sampleRule, id: 'slack' }); + expect(preferences.getRecordingAppPolicy().denylist).toHaveLength(1); + + preferences.removeRecordingAppRule('allowlist', 'zoom'); + expect(preferences.getRecordingAppPolicy().allowlist).toHaveLength(0); + }); + + it('updates project filters and simple flags', () => { + preferences.setMeetingsProjectFilter('selected', ['project-1']); + preferences.setTasksProjectFilter('selected', ['project-2']); + preferences.setTasksViewMode('board'); + preferences.setSimulateTranscription(true); + preferences.setSkipSimulationConfirmation(true); + + const prefs = preferences.get(); + expect(prefs.meetings_project_scope).toBe('selected'); + expect(prefs.tasks_project_scope).toBe('selected'); + expect(prefs.tasks_view_mode).toBe('board'); + expect(preferences.shouldSkipSimulationConfirmation()).toBe(true); + }); + + it('manages integrations and tasks', () => { + const integration = preferences.addCustomIntegration('Custom', { url: 'https://example.com' }); + expect(integration.id).toBe('custom-id'); + expect(generateId).toHaveBeenCalled(); + + preferences.updateIntegration(integration.id, { status: 'connected' }); + expect( + preferences.getIntegrations().find((item) => item.id === integration.id)?.status + ).toBe('connected'); + + const firstToggle = preferences.toggleTaskCompletion('meeting-1', 'Do the thing'); + expect(firstToggle).toBe(true); + expect(preferences.isTaskCompleted('meeting-1', 'Do the thing')).toBe(true); + + const secondToggle = preferences.toggleTaskCompletion('meeting-1', 'Do the thing'); + expect(secondToggle).toBe(false); + expect(preferences.isTaskCompleted('meeting-1', 'Do the thing')).toBe(false); + + preferences.removeIntegration(integration.id); + expect(preferences.getIntegrations().some((item) => item.id === integration.id)).toBe(false); + }); + + it('manages speaker names and sync settings', () => { + preferences.setGlobalSpeakerName('speaker-1', 'Global Name'); + expect(preferences.getGlobalSpeakerName('speaker-1')).toBe('Global Name'); + + preferences.setSpeakerName('meeting-1', 'speaker-1', 'Meeting Name'); + expect(preferences.getSpeakerName('meeting-1', 'speaker-1')).toBe('Meeting Name'); + expect(preferences.getSpeakerName('meeting-2', 'speaker-1')).toBe('Global Name'); + + preferences.updateSyncNotifications({ enabled: false }); + expect(preferences.getSyncNotifications().enabled).toBe(false); + + preferences.setSyncSchedulerPaused(true); + expect(preferences.isSyncSchedulerPaused()).toBe(true); + + const event: SyncHistoryEvent = { + type: 'sync_started', + timestamp: 123456, + message: 'Sync start', + }; + preferences.addSyncHistoryEvent(event); + expect(preferences.getSyncHistory()).toHaveLength(1); + preferences.clearSyncHistory(); + expect(preferences.getSyncHistory()).toHaveLength(0); + }); +}); diff --git a/client/src/lib/preferences/api.ts b/client/src/lib/preferences/api.ts index a53600d..8331318 100644 --- a/client/src/lib/preferences/api.ts +++ b/client/src/lib/preferences/api.ts @@ -1,24 +1,14 @@ // Preferences API. - import { isRecord } from '@/api'; import { generateId } from '@/api/adapters/mock/data'; import type { - AITemplate, - ExportFormat, - Integration, - RecordingAppPolicy, - RecordingAppRule, - SyncHistoryEvent, - SyncNotificationPreferences, - TaskCompletion, - UserPreferences, + AITemplate, ExportFormat, Integration, RecordingAppPolicy, RecordingAppRule, + SyncHistoryEvent, SyncNotificationPreferences, TaskCompletion, UserPreferences, } from '@/api/types'; import { addClientLog } from '@/lib/observability/client'; import { buildServerUrl } from '@/lib/config'; import { debug } from '@/lib/observability/debug'; - const log = debug('Preferences:Replace'); - import { arePreferencesEqual, clonePreferences } from './core'; import { defaultPreferences } from './constants'; import { mergeIntegrationsWithDefaults, resetIntegrationsForServerSwitch } from './integrations'; @@ -265,6 +255,12 @@ export const preferences = { }); }, + setTasksViewMode(mode: UserPreferences['tasks_view_mode']): void { + withPreferences((prefs) => { + prefs.tasks_view_mode = mode; + }); + }, + // SIMPLE TOP-LEVEL SETTERS setSimulateTranscription(enabled: boolean): void { withPreferences((prefs) => { diff --git a/client/src/lib/preferences/constants.ts b/client/src/lib/preferences/constants.ts index c77f2f1..63f1557 100644 --- a/client/src/lib/preferences/constants.ts +++ b/client/src/lib/preferences/constants.ts @@ -77,4 +77,5 @@ export const defaultPreferences: UserPreferences = { meetings_project_ids: [], tasks_project_scope: 'active', tasks_project_ids: [], + tasks_view_mode: 'list', }; diff --git a/client/src/lib/preferences/integrations.test.ts b/client/src/lib/preferences/integrations.test.ts new file mode 100644 index 0000000..a24e3c5 --- /dev/null +++ b/client/src/lib/preferences/integrations.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import type { Integration } from '@/api/types'; +import { DEFAULT_INTEGRATIONS } from '@/lib/integrations/defaults'; +import { mergeIntegrationsWithDefaults, resetIntegrationsForServerSwitch } from './integrations'; + +const buildCustomIntegration = (): Integration => ({ + id: 'custom-1', + name: 'Custom Tool', + type: 'custom', + status: 'connected', + webhook_config: { url: 'https://example.com', method: 'POST' }, +}); + +describe('integration preference helpers', () => { + it('merges incoming integrations with defaults', () => { + const incoming = [ + { ...DEFAULT_INTEGRATIONS[0], status: 'connected', integration_id: 'srv-1' }, + buildCustomIntegration(), + ]; + + const merged = mergeIntegrationsWithDefaults(incoming); + + const mergedDefault = merged.find((integration) => integration.name === DEFAULT_INTEGRATIONS[0].name); + expect(mergedDefault?.status).toBe('connected'); + expect(mergedDefault?.integration_id).toBe('srv-1'); + expect(merged.some((integration) => integration.name === 'Custom Tool')).toBe(true); + }); + + it('resets integrations for server switch', () => { + const incoming = [ + { ...DEFAULT_INTEGRATIONS[0], status: 'connected', integration_id: 'srv-1' }, + buildCustomIntegration(), + ]; + + const reset = resetIntegrationsForServerSwitch(incoming); + + const defaultReset = reset.find((integration) => integration.name === DEFAULT_INTEGRATIONS[0].name); + expect(defaultReset?.status).toBe('disconnected'); + expect(defaultReset?.integration_id).toBeUndefined(); + + const customReset = reset.find((integration) => integration.name === 'Custom Tool'); + expect(customReset?.status).toBe('disconnected'); + expect(customReset?.integration_id).toBeUndefined(); + }); +}); diff --git a/client/src/lib/preferences/local-only-keys.test.ts b/client/src/lib/preferences/local-only-keys.test.ts new file mode 100644 index 0000000..1529da1 --- /dev/null +++ b/client/src/lib/preferences/local-only-keys.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; +import { LOCAL_ONLY_PREFERENCE_KEYS, isLocalOnlyKey } from './local-only-keys'; + +describe('local-only preference keys', () => { + it('includes expected keys', () => { + expect(LOCAL_ONLY_PREFERENCE_KEYS.has('audio_devices')).toBe(true); + expect(LOCAL_ONLY_PREFERENCE_KEYS.has('default_export_location')).toBe(true); + }); + + it('detects local-only keys with type guard', () => { + expect(isLocalOnlyKey('audio_devices')).toBe(true); + expect(isLocalOnlyKey('server_host')).toBe(false); + }); +}); diff --git a/client/src/lib/preferences/storage.test.ts b/client/src/lib/preferences/storage.test.ts new file mode 100644 index 0000000..123ad9e --- /dev/null +++ b/client/src/lib/preferences/storage.test.ts @@ -0,0 +1,270 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Integration, UserPreferences } from '@/api/types'; +import { + MODEL_CATALOG_CACHE_KEY, + PREFERENCES_KEY, + PREFERENCES_LOCAL_CACHE_KEY, + SERVER_ADDRESS_OVERRIDE_KEY, + SERVER_ADDRESS_VALUE_KEY, +} from '@/lib/storage/keys'; +import { defaultPreferences } from './constants'; + +const mocks = vi.hoisted(() => ({ + isTauriRuntime: vi.fn(), + addClientLog: vi.fn(), +})); + +vi.mock('./core', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isTauriRuntime: mocks.isTauriRuntime, + }; +}); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: mocks.addClientLog, +})); + +const clonePrefs = (): UserPreferences => + JSON.parse(JSON.stringify(defaultPreferences)) as UserPreferences; + +describe('preferences storage', () => { + let storage: typeof import('./storage'); + + beforeEach(async () => { + vi.resetModules(); + mocks.isTauriRuntime.mockReset(); + mocks.isTauriRuntime.mockReturnValue(false); + mocks.addClientLog.mockReset(); + localStorage.clear(); + storage = await import('./storage'); + storage._resetPreferencesForTesting(); + storage.getListeners().clear(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('loads server address override when enabled', () => { + localStorage.setItem(SERVER_ADDRESS_OVERRIDE_KEY, 'true'); + localStorage.setItem( + SERVER_ADDRESS_VALUE_KEY, + JSON.stringify({ host: 'localhost', port: '5000', updated_at: 123 }) + ); + + expect(storage.loadServerAddressOverride()).toEqual({ + host: 'localhost', + port: '5000', + updated_at: 123, + }); + }); + + it('returns null for disabled or invalid server overrides', () => { + expect(storage.loadServerAddressOverride()).toBeNull(); + + localStorage.setItem(SERVER_ADDRESS_OVERRIDE_KEY, 'true'); + localStorage.setItem( + SERVER_ADDRESS_VALUE_KEY, + JSON.stringify({ host: '', port: '5000', updated_at: 123 }) + ); + expect(storage.loadServerAddressOverride()).toBeNull(); + + localStorage.setItem(SERVER_ADDRESS_VALUE_KEY, 'not-json'); + expect(storage.loadServerAddressOverride()).toBeNull(); + }); + + it('sets and clears server address overrides', () => { + const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(987); + + storage.setServerAddressOverride(true, 'host', '123'); + expect(localStorage.getItem(SERVER_ADDRESS_OVERRIDE_KEY)).toBe('true'); + const stored = JSON.parse(localStorage.getItem(SERVER_ADDRESS_VALUE_KEY) ?? '{}'); + expect(stored).toEqual({ host: 'host', port: '123', updated_at: 987 }); + + storage.setServerAddressOverride(false, '', ''); + expect(localStorage.getItem(SERVER_ADDRESS_OVERRIDE_KEY)).toBeNull(); + expect(localStorage.getItem(SERVER_ADDRESS_VALUE_KEY)).toBeNull(); + + nowSpy.mockRestore(); + }); + + it('logs when override storage fails', () => { + const setItemSpy = vi + .spyOn(Storage.prototype, 'setItem') + .mockImplementation(() => { + throw new Error('fail'); + }); + + storage.setServerAddressOverride(true, 'host', '123'); + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Server address override storage failed' }) + ); + + setItemSpy.mockRestore(); + }); + + it('applies local overrides to preferences', () => { + localStorage.setItem(SERVER_ADDRESS_OVERRIDE_KEY, 'true'); + localStorage.setItem( + SERVER_ADDRESS_VALUE_KEY, + JSON.stringify({ host: 'override', port: '9999', updated_at: 42 }) + ); + + const updated = storage.applyLocalOverrides(clonePrefs()); + expect(updated.server_host).toBe('override'); + expect(updated.server_port).toBe('9999'); + expect(updated.server_address_customized).toBe(true); + expect(updated.server_address_customized_at).toBe(42); + }); + + it('loads and hydrates stored preferences', () => { + const defaultIntegration = defaultPreferences.integrations[0]; + const storedIntegration: Integration = { + ...defaultIntegration, + oauth_config: undefined, + }; + const customIntegration: Integration = { + id: 'custom', + name: 'Custom Integration', + type: 'pkm', + status: 'connected', + pkm_config: { vault_path: '/vault', sync_enabled: true }, + }; + const invalidRecordingPolicy = { allowlist: 'bad', denylist: null }; + const invalidAudioDevices = { input_device_id: 'mic-1' }; + const invalidTemplate = { tone: 'friendly', format: 'invalid', verbosity: 'minimal' }; + + const storedPrefs: Partial = { + server_host: 'custom', + server_port: '9876', + integrations: [storedIntegration, customIntegration], + recording_app_policy: + invalidRecordingPolicy as UserPreferences['recording_app_policy'], + audio_devices: invalidAudioDevices as UserPreferences['audio_devices'], + ai_template: invalidTemplate as UserPreferences['ai_template'], + }; + + localStorage.setItem(PREFERENCES_KEY, JSON.stringify(storedPrefs)); + const loaded = storage.loadPreferences(); + + expect(loaded.server_address_customized).toBe(true); + expect(loaded.server_address_customized_at).toBeNull(); + const mergedIntegration = loaded.integrations.find((i) => i.name === defaultIntegration.name); + expect(mergedIntegration?.oauth_config).toBeTruthy(); + expect(loaded.integrations.some((i) => i.name === 'Custom Integration')).toBe(true); + expect(loaded.recording_app_policy.allowlist).toEqual([]); + expect(loaded.audio_devices.output_device_id).toBe( + defaultPreferences.audio_devices.output_device_id + ); + expect(loaded.ai_template.format).toBe(defaultPreferences.ai_template.format); + }); + + it('falls back to defaults when stored preferences are invalid', () => { + localStorage.setItem(PREFERENCES_KEY, '{bad json'); + const loaded = storage.loadPreferences(); + expect(loaded.server_host).toBe(defaultPreferences.server_host); + }); + + it('applies overrides during load', () => { + localStorage.setItem( + PREFERENCES_KEY, + JSON.stringify({ server_host: 'custom', server_port: '7777' }) + ); + localStorage.setItem(SERVER_ADDRESS_OVERRIDE_KEY, 'true'); + localStorage.setItem( + SERVER_ADDRESS_VALUE_KEY, + JSON.stringify({ host: 'override', port: '1111', updated_at: 555 }) + ); + const loaded = storage.loadPreferences(); + expect(loaded.server_host).toBe('override'); + expect(loaded.server_port).toBe('1111'); + expect(loaded.server_address_customized_at).toBe(555); + }); + + it('returns cloned in-memory preferences', () => { + const prefs = clonePrefs(); + prefs.server_host = 'memory'; + storage.setInMemoryPrefs(prefs); + const loaded = storage.loadPreferences(); + expect(loaded).not.toBe(prefs); + expect(loaded.server_host).toBe('memory'); + }); + + it('saves preferences, notifies listeners, and persists to tauri', () => { + mocks.isTauriRuntime.mockReturnValue(true); + const persistToTauri = vi.fn(async () => undefined); + const listener = vi.fn(); + storage.getListeners().add(listener); + const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(123); + + const prefs = clonePrefs(); + storage.savePreferences(prefs, persistToTauri); + + const savedRaw = localStorage.getItem(PREFERENCES_KEY); + expect(savedRaw).not.toBeNull(); + const saved = JSON.parse(savedRaw ?? '{}') as UserPreferences; + expect(saved.preferences_updated_at).toBe(123); + expect(listener).toHaveBeenCalledWith(expect.objectContaining({ preferences_updated_at: 123 })); + expect(persistToTauri).toHaveBeenCalled(); + + nowSpy.mockRestore(); + }); + + it('logs when saving preferences fails', () => { + const setItemSpy = vi + .spyOn(Storage.prototype, 'setItem') + .mockImplementation(() => { + throw new Error('fail'); + }); + const persistToTauri = vi.fn(async () => undefined); + + storage.savePreferences(clonePrefs(), persistToTauri); + + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Storage save failed - continuing with in-memory cache' }) + ); + + setItemSpy.mockRestore(); + }); + + it('manages local cache and export location helpers', () => { + expect(storage.loadLocalCache()).toEqual({}); + + storage.saveLocalCache({ tauri_export_location: '/exports' }); + expect(storage.getTauriExportLocationCache()).toBe('/exports'); + + storage.setTauriExportLocationCache('/new-path'); + expect(storage.getTauriExportLocationCache()).toBe('/new-path'); + }); + + it('returns empty cache on invalid local cache data', () => { + localStorage.setItem(PREFERENCES_LOCAL_CACHE_KEY, 'not-json'); + expect(storage.loadLocalCache()).toEqual({}); + }); + + it('clears local caches and logs on failure', () => { + localStorage.setItem(PREFERENCES_LOCAL_CACHE_KEY, 'value'); + localStorage.setItem(SERVER_ADDRESS_OVERRIDE_KEY, 'true'); + localStorage.setItem(SERVER_ADDRESS_VALUE_KEY, 'value'); + localStorage.setItem(MODEL_CATALOG_CACHE_KEY, 'value'); + + storage.clearLocalCaches(); + expect(localStorage.getItem(PREFERENCES_LOCAL_CACHE_KEY)).toBeNull(); + expect(localStorage.getItem(SERVER_ADDRESS_OVERRIDE_KEY)).toBeNull(); + expect(localStorage.getItem(SERVER_ADDRESS_VALUE_KEY)).toBeNull(); + expect(localStorage.getItem(MODEL_CATALOG_CACHE_KEY)).toBeNull(); + + const removeSpy = vi + .spyOn(Storage.prototype, 'removeItem') + .mockImplementation(() => { + throw new Error('fail'); + }); + storage.clearLocalCaches(); + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Failed to clear local preferences cache' }) + ); + removeSpy.mockRestore(); + }); +}); diff --git a/client/src/lib/preferences/tags.test.ts b/client/src/lib/preferences/tags.test.ts new file mode 100644 index 0000000..d22c304 --- /dev/null +++ b/client/src/lib/preferences/tags.test.ts @@ -0,0 +1,69 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { UserPreferences } from '@/api/types'; +import { buildPreferences } from '@/api/adapters/tauri/__tests__/test-utils'; + +const savePreferences = vi.fn(); +const loadPreferences = vi.fn(); +const persistPreferencesToTauri = vi.fn(); + +vi.mock('@/api/adapters/mock/data', () => ({ + generateId: () => 'tag-new', +})); + +vi.mock('./storage', () => ({ + loadPreferences: () => loadPreferences(), + savePreferences: (...args: unknown[]) => savePreferences(...args), +})); + +vi.mock('./tauri', () => ({ + persistPreferencesToTauri: (...args: unknown[]) => persistPreferencesToTauri(...args), +})); + +describe('createTagOperations', () => { + let prefs: UserPreferences; + + beforeEach(() => { + prefs = buildPreferences(); + prefs.tags = [ + { id: 'tag-1', name: 'Alpha', color: 'red', meeting_ids: ['m1'] }, + { id: 'tag-2', name: 'Beta', color: 'blue', meeting_ids: [] }, + ]; + loadPreferences.mockReturnValue(prefs); + savePreferences.mockClear(); + persistPreferencesToTauri.mockClear(); + }); + + it('returns and mutates tags', async () => { + vi.resetModules(); + const withPreferences = (updater: (prefs: UserPreferences) => void) => { + updater(prefs); + savePreferences(prefs, persistPreferencesToTauri); + }; + const { createTagOperations } = await import('@/lib/preferences/tags'); + const operations = createTagOperations(withPreferences); + + const initialTags = operations.getTags(); + expect(initialTags.some((tag) => tag.id === 'tag-1')).toBe(true); + + const created = operations.addTag('Gamma', 'green'); + expect(created.name).toBe('Gamma'); + expect(created.color).toBe('green'); + expect(created.meeting_ids).toEqual([]); + expect(savePreferences).toHaveBeenCalled(); + + operations.deleteTag('tag-2'); + expect(prefs.tags.some((tag) => tag.id === 'tag-2')).toBe(false); + + operations.addMeetingToTag('tag-1', 'm2'); + operations.addMeetingToTag('tag-1', 'm2'); + const tagOne = prefs.tags.find((tag) => tag.id === 'tag-1'); + expect(tagOne?.meeting_ids.includes('m2')).toBe(true); + + operations.removeMeetingFromTag('tag-1', 'm1'); + expect(tagOne?.meeting_ids.includes('m1')).toBe(false); + + const meetingTags = operations.getMeetingTags('m2'); + expect(meetingTags.some((tag) => tag.id === 'tag-1')).toBe(true); + }); +}); diff --git a/client/src/lib/preferences/tauri.test.ts b/client/src/lib/preferences/tauri.test.ts new file mode 100644 index 0000000..acbbf27 --- /dev/null +++ b/client/src/lib/preferences/tauri.test.ts @@ -0,0 +1,256 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Integration, UserPreferences } from '@/api/types'; +import { defaultPreferences } from './constants'; + +const mocks = vi.hoisted(() => ({ + invoke: vi.fn(), + isTauriRuntime: vi.fn(), + loadPreferences: vi.fn(), + emitValidationEvent: vi.fn(), + addClientLog: vi.fn(), + extractErrorMessage: vi.fn((err: unknown, fallback: string) => + err instanceof Error ? err.message : fallback + ), +})); + +vi.mock('@tauri-apps/api/core', () => ({ + invoke: mocks.invoke, +})); + +vi.mock('./core', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isTauriRuntime: mocks.isTauriRuntime, + }; +}); + +vi.mock('./storage', () => ({ + loadPreferences: mocks.loadPreferences, +})); + +vi.mock('./validation-events', () => ({ + emitValidationEvent: mocks.emitValidationEvent, +})); + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: mocks.addClientLog, +})); + +vi.mock('@/api', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + extractErrorMessage: mocks.extractErrorMessage, + }; +}); + +vi.mock('@/lib/observability/debug', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + debug: () => () => undefined, + }; +}); + +const clonePrefs = (): UserPreferences => + JSON.parse(JSON.stringify(defaultPreferences)) as UserPreferences; + +describe('preferences tauri', () => { + let tauri: typeof import('./tauri'); + + beforeEach(async () => { + vi.resetModules(); + mocks.invoke.mockReset(); + mocks.isTauriRuntime.mockReset(); + mocks.loadPreferences.mockReset(); + mocks.emitValidationEvent.mockReset(); + mocks.addClientLog.mockReset(); + mocks.extractErrorMessage.mockClear(); + tauri = await import('./tauri'); + tauri._resetTauriHydrationForTesting(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('returns hydrated in non-tauri runtime', () => { + mocks.isTauriRuntime.mockReturnValue(false); + expect(tauri.isHydrated()).toBe(true); + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'isHydrated: non-Tauri runtime, returning true' }) + ); + }); + + it('waits for hydration before resolving', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + const localPrefs = clonePrefs(); + mocks.loadPreferences.mockReturnValue(localPrefs); + mocks.invoke.mockResolvedValue(clonePrefs()); + + const pending = tauri.waitForHydration(); + await tauri.hydratePreferencesFromTauri(vi.fn()); + + await expect(pending).resolves.toBeUndefined(); + expect(tauri.isHydrated()).toBe(true); + }); + + it('debounces persistence and merges audio devices', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + const storedPrefs = clonePrefs(); + storedPrefs.audio_devices = { + ...storedPrefs.audio_devices, + input_device_id: 'stored-input', + output_device_id: 'stored-output', + }; + mocks.loadPreferences.mockReturnValue(storedPrefs); + mocks.invoke.mockResolvedValue(undefined); + vi.useFakeTimers(); + + const nextPrefs = clonePrefs(); + nextPrefs.audio_devices = { + ...nextPrefs.audio_devices, + input_device_id: 'new-input', + output_device_id: 'new-output', + }; + const secondPrefs = { ...nextPrefs, server_host: 'latest-host' }; + + void tauri.persistPreferencesToTauri(nextPrefs); + const pending = tauri.persistPreferencesToTauri(secondPrefs); + + await vi.advanceTimersByTimeAsync(500); + await pending; + + expect(mocks.invoke).toHaveBeenCalledTimes(1); + const [command, payload] = mocks.invoke.mock.calls[0]; + expect(command).toBe('save_preferences'); + expect(payload.preferences.audio_devices.input_device_id).toBe('stored-input'); + expect(payload.preferences.audio_devices.output_device_id).toBe('stored-output'); + expect(payload.preferences.server_host).toBe('latest-host'); + }); + + it('logs warning after repeated persistence failures', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + mocks.loadPreferences.mockReturnValue(clonePrefs()); + mocks.invoke.mockRejectedValue(new Error('disk error')); + vi.useFakeTimers(); + + const first = tauri.persistPreferencesToTauri(clonePrefs()); + await vi.advanceTimersByTimeAsync(500); + await first; + + const second = tauri.persistPreferencesToTauri(clonePrefs()); + await vi.advanceTimersByTimeAsync(500); + await second; + + const third = tauri.persistPreferencesToTauri(clonePrefs()); + await vi.advanceTimersByTimeAsync(500); + await third; + + expect(mocks.addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Preferences may not persist across app restarts' }) + ); + }); + + it('hydrates preferences and preserves local-only keys', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + const localPrefs = clonePrefs(); + localPrefs.default_export_location = '/local/exports'; + const tauriPrefs = clonePrefs(); + tauriPrefs.default_export_location = '/tauri/exports'; + tauriPrefs.audio_devices = { + ...tauriPrefs.audio_devices, + input_device_id: 'tauri-input', + output_device_id: 'tauri-output', + }; + mocks.loadPreferences.mockReturnValue(localPrefs); + mocks.invoke.mockResolvedValue(tauriPrefs); + + const replacePrefs = vi.fn(); + await tauri.hydratePreferencesFromTauri(replacePrefs); + + expect(replacePrefs).toHaveBeenCalledWith( + expect.objectContaining({ + default_export_location: '/local/exports', + audio_devices: expect.objectContaining({ + input_device_id: 'tauri-input', + output_device_id: 'tauri-output', + }), + }) + ); + }); + + it('removes stale integrations and emits event', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + const prefs = clonePrefs(); + const activeIntegration: Integration = { + ...prefs.integrations[0], + integration_id: 'server-1', + }; + const staleIntegration: Integration = { + ...prefs.integrations[1], + id: 'stale', + name: 'Stale Integration', + integration_id: 'server-missing', + }; + const localOnlyIntegration: Integration = { + ...prefs.integrations[2], + id: 'local-only', + name: 'Local Integration', + integration_id: '', + }; + prefs.integrations = [activeIntegration, staleIntegration, localOnlyIntegration]; + mocks.loadPreferences.mockReturnValue(prefs); + mocks.invoke.mockResolvedValue({ integrations: [{ id: activeIntegration.integration_id }] }); + + const replacePrefs = vi.fn(); + await tauri.validateCachedIntegrations(replacePrefs); + + expect(replacePrefs).toHaveBeenCalledWith( + expect.objectContaining({ + integrations: [activeIntegration, localOnlyIntegration], + }) + ); + expect(mocks.emitValidationEvent).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'integrations_removed', + removedIntegrationIds: ['stale'], + removedIntegrationNames: ['Stale Integration'], + }) + ); + }); + + it('emits completion when no integrations removed', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + const prefs = clonePrefs(); + mocks.loadPreferences.mockReturnValue(prefs); + mocks.invoke.mockResolvedValue({ + integrations: prefs.integrations.map((integration) => ({ + id: integration.integration_id ?? '', + })), + }); + + const replacePrefs = vi.fn(); + await tauri.validateCachedIntegrations(replacePrefs); + + expect(replacePrefs).not.toHaveBeenCalled(); + expect(mocks.emitValidationEvent).toHaveBeenCalledWith( + expect.objectContaining({ type: 'validation_complete' }) + ); + }); + + it('emits failure event when validation fails', async () => { + mocks.isTauriRuntime.mockReturnValue(true); + mocks.loadPreferences.mockReturnValue(clonePrefs()); + mocks.invoke.mockRejectedValue(new Error('boom')); + mocks.extractErrorMessage.mockReturnValue('boom'); + + const replacePrefs = vi.fn(); + await tauri.validateCachedIntegrations(replacePrefs); + + expect(mocks.emitValidationEvent).toHaveBeenCalledWith( + expect.objectContaining({ type: 'validation_failed', error: 'boom' }) + ); + }); +}); diff --git a/client/src/lib/storage/crypto.test.ts b/client/src/lib/storage/crypto.test.ts index 5d5fe21..c79c1c8 100644 --- a/client/src/lib/storage/crypto.test.ts +++ b/client/src/lib/storage/crypto.test.ts @@ -3,10 +3,20 @@ import { clearSecureStorage, decryptData, encryptData, + exportCredentialsBackup, getSecureValue, + importCredentialsBackup, isSecureStorageAvailable, + migrateSecureStorage, + checkSecureStorageHealth, setSecureValue, } from '@/lib/storage/crypto'; +import { addClientLog } from '@/lib/observability/client'; +import { DEVICE_ID_KEY, SECURE_DATA_KEY } from '@/lib/storage/keys'; + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); const toArrayBuffer = (input: ArrayBuffer | ArrayBufferView): ArrayBuffer => { if (input instanceof ArrayBuffer) { @@ -98,4 +108,80 @@ describe('crypto utilities', () => { vi.stubGlobal('crypto', mockCrypto as Crypto); expect(isSecureStorageAvailable()).toBe(true); }); + + it('returns empty string when secure store is corrupted', async () => { + const encrypted = await encryptData('not-json'); + localStorage.setItem(SECURE_DATA_KEY, encrypted); + expect(await getSecureValue('token')).toBe(''); + }); + + it('exports and imports credential backups', async () => { + await setSecureValue('token', 'abc123'); + const backup = await exportCredentialsBackup('passphrase'); + expect(typeof backup).toBe('string'); + + localStorage.removeItem(SECURE_DATA_KEY); + await importCredentialsBackup(backup, 'passphrase'); + + expect(await getSecureValue('token')).toBe('abc123'); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Credentials imported from backup successfully' }) + ); + }); + + it('throws when exporting with no credentials', async () => { + await expect(exportCredentialsBackup('passphrase')).rejects.toThrow('No credentials to export'); + }); + + it('rejects invalid backup inputs', async () => { + await expect(importCredentialsBackup(btoa('short'), 'passphrase')).rejects.toThrow( + 'Invalid backup format - data too short' + ); + }); + + it('handles secure storage migration paths', async () => { + const encrypted = await encryptData('data'); + localStorage.setItem(SECURE_DATA_KEY, encrypted); + + expect(await migrateSecureStorage()).toBe(true); + + const decryptMock = vi + .mocked(mockCrypto.subtle.decrypt) + .mockRejectedValueOnce(new Error('boom')) + .mockResolvedValueOnce(toArrayBuffer(new TextEncoder().encode('legacy-data'))); + + const migrated = await migrateSecureStorage(); + expect(migrated).toBe(true); + expect(localStorage.getItem(DEVICE_ID_KEY)).not.toBeNull(); + + decryptMock.mockRejectedValueOnce(new Error('boom')).mockRejectedValueOnce(new Error('boom')); + const failed = await migrateSecureStorage(); + expect(failed).toBe(false); + }); + + it('reports secure storage health states', async () => { + const unavailableCrypto: Partial = {}; + vi.stubGlobal('crypto', unavailableCrypto as Crypto); + expect(await checkSecureStorageHealth()).toBe('unavailable'); + vi.stubGlobal('crypto', mockCrypto as Crypto); + + localStorage.removeItem(SECURE_DATA_KEY); + expect(await checkSecureStorageHealth()).toBe('empty'); + + const encrypted = await encryptData(JSON.stringify({ token: 'ok' })); + localStorage.setItem(SECURE_DATA_KEY, encrypted); + + vi.mocked(mockCrypto.subtle.decrypt).mockRejectedValueOnce(new Error('boom')); + expect(await checkSecureStorageHealth()).toBe('key_mismatch'); + + vi.mocked(mockCrypto.subtle.decrypt).mockResolvedValueOnce( + toArrayBuffer(new TextEncoder().encode(JSON.stringify({ token: 123 }))) + ); + expect(await checkSecureStorageHealth()).toBe('corrupted'); + + vi.mocked(mockCrypto.subtle.decrypt).mockResolvedValueOnce( + toArrayBuffer(new TextEncoder().encode(JSON.stringify({ token: 'ok' }))) + ); + expect(await checkSecureStorageHealth()).toBe('healthy'); + }); }); diff --git a/client/src/lib/storage/utils.test.ts b/client/src/lib/storage/utils.test.ts new file mode 100644 index 0000000..07af7ab --- /dev/null +++ b/client/src/lib/storage/utils.test.ts @@ -0,0 +1,91 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { + clearStorageByPrefix, + readStorage, + readStorageRaw, + removeStorage, + writeStorage, + writeStorageRaw, +} from './utils'; +import { addClientLog } from '@/lib/observability/client'; + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +describe('storage utils', () => { + beforeEach(() => { + localStorage.clear(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); + }); + + it('reads and writes JSON values', () => { + expect(readStorage('missing', { ok: true })).toEqual({ ok: true }); + expect(writeStorage('key', { value: 1 })).toBe(true); + expect(readStorage('key', null)).toEqual({ value: 1 }); + }); + + it('returns fallback when JSON is invalid', () => { + localStorage.setItem('bad', '{oops'); + expect(readStorage('bad', { fallback: true })).toEqual({ fallback: true }); + }); + + it('logs and returns false on write errors', () => { + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new Error('boom'); + }); + + expect(writeStorage('key', { value: 2 }, 'ctx')).toBe(false); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Storage write failed for key "key"' }) + ); + }); + + it('removes values and logs on errors', () => { + localStorage.setItem('remove', '1'); + expect(removeStorage('remove', 'ctx')).toBe(true); + + vi.spyOn(Storage.prototype, 'removeItem').mockImplementation(() => { + throw new Error('boom'); + }); + + expect(removeStorage('bad', 'ctx')).toBe(false); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Storage remove failed for key "bad"' }) + ); + }); + + it('clears keys by prefix', () => { + localStorage.setItem('prefix-one', '1'); + localStorage.setItem('prefix-two', '2'); + localStorage.setItem('other', '3'); + + const removed = clearStorageByPrefix('prefix', 'ctx'); + + expect(removed).toBe(2); + expect(localStorage.getItem('prefix-one')).toBeNull(); + expect(localStorage.getItem('prefix-two')).toBeNull(); + expect(localStorage.getItem('other')).toBe('3'); + }); + + it('handles raw storage helpers', () => { + expect(readStorageRaw('raw', 'fallback')).toBe('fallback'); + expect(writeStorageRaw('raw', 'value', 'ctx')).toBe(true); + expect(readStorageRaw('raw', 'fallback')).toBe('value'); + }); + + it('logs raw write errors', () => { + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new Error('boom'); + }); + + expect(writeStorageRaw('raw', 'value', 'ctx')).toBe(false); + expect(addClientLog).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Storage write (raw) failed for key "raw"' }) + ); + }); +}); diff --git a/client/src/lib/system/events.test.tsx b/client/src/lib/system/events.test.tsx index c51dce4..4c7ef25 100644 --- a/client/src/lib/system/events.test.tsx +++ b/client/src/lib/system/events.test.tsx @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'; // Use vi.hoisted to ensure mock values are available before module imports // Note: Values must match the real TauriEvents values (UPPER_CASE) @@ -60,7 +60,7 @@ function emitTauriEvent(eventName: string, payload: unknown): void { } describe('tauri-events bridge', () => { - let isTauriEnvSpy: ReturnType; + let isTauriEnvSpy: MockInstance<[], boolean>; let mockListen: TauriListenFn; beforeEach(() => { diff --git a/client/src/lib/utils/event-emitter.test.ts b/client/src/lib/utils/event-emitter.test.ts new file mode 100644 index 0000000..17a4e90 --- /dev/null +++ b/client/src/lib/utils/event-emitter.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createEventEmitter, createMultiEventEmitter } from '@/lib/utils/event-emitter'; +import { addClientLog } from '@/lib/observability/client'; + +vi.mock('@/lib/observability/client', () => ({ + addClientLog: vi.fn(), +})); + +describe('createEventEmitter', () => { + it('subscribes, emits, and unsubscribes listeners', () => { + const emitter = createEventEmitter<{ value: string }>('Test'); + const listener = vi.fn(); + + const unsubscribe = emitter.subscribe(listener); + expect(emitter.subscriberCount()).toBe(1); + + emitter.emit({ value: 'ping' }); + expect(listener).toHaveBeenCalledWith({ value: 'ping' }); + + unsubscribe(); + expect(emitter.subscriberCount()).toBe(0); + }); + + it('clears listeners and logs listener errors', () => { + const emitter = createEventEmitter('Errors'); + const goodListener = vi.fn(); + const badListener = vi.fn(() => { + throw new Error('boom'); + }); + + emitter.subscribe(goodListener); + emitter.subscribe(badListener); + + emitter.emit('event'); + expect(goodListener).toHaveBeenCalledWith('event'); + expect(addClientLog).toHaveBeenCalled(); + + emitter.clear(); + expect(emitter.subscriberCount()).toBe(0); + }); +}); + +describe('createMultiEventEmitter', () => { + it('emits to specific event listeners and supports off/clear', () => { + const events = createMultiEventEmitter<{ ready: boolean; updated: number }>('Multi'); + const readyListener = vi.fn(); + const updatedListener = vi.fn(); + + const unsubscribeReady = events.on('ready', readyListener); + events.on('updated', updatedListener); + + events.emit('ready', true); + events.emit('updated', 3); + + expect(readyListener).toHaveBeenCalledWith(true); + expect(updatedListener).toHaveBeenCalledWith(3); + + unsubscribeReady(); + events.emit('ready', false); + expect(readyListener).toHaveBeenCalledTimes(1); + + events.off('updated'); + events.emit('updated', 9); + expect(updatedListener).toHaveBeenCalledTimes(1); + + events.clear(); + events.emit('ready', true); + events.emit('updated', 4); + expect(updatedListener).toHaveBeenCalledTimes(1); + }); +}); diff --git a/client/src/lib/utils/id.test.ts b/client/src/lib/utils/id.test.ts new file mode 100644 index 0000000..edb2af6 --- /dev/null +++ b/client/src/lib/utils/id.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it, vi } from 'vitest'; +import { generateUuid } from './id'; + +describe('generateUuid', () => { + it('uses crypto.randomUUID when available', () => { + const randomUUID = vi.fn(() => 'uuid'); + const cryptoMock = { randomUUID }; + vi.stubGlobal('crypto', cryptoMock as Crypto); + + expect(generateUuid()).toBe('uuid'); + expect(randomUUID).toHaveBeenCalled(); + }); + + it('falls back to template generation when randomUUID is unavailable', () => { + const cryptoMock = {}; + vi.stubGlobal('crypto', cryptoMock as Crypto); + vi.spyOn(Math, 'random').mockReturnValue(0); + + expect(generateUuid()).toBe('00000000-0000-4000-8000-000000000000'); + }); +}); diff --git a/client/src/lib/utils/polling.test.ts b/client/src/lib/utils/polling.test.ts new file mode 100644 index 0000000..a91d8f1 --- /dev/null +++ b/client/src/lib/utils/polling.test.ts @@ -0,0 +1,189 @@ +import { act, renderHook } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createInFlightGuard, createPoller, usePoller } from '@/lib/utils/polling'; + +const INTERVAL_MS = 100; +const MAX_DURATION_MS = 250; + +async function flushTimers(ms: number) { + await act(async () => { + await vi.advanceTimersByTimeAsync(ms); + }); +} + +describe('usePoller', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-01-01T00:00:00.000Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('starts polling and stops when pollFn returns false', async () => { + const pollFn = vi.fn().mockResolvedValueOnce(false); + const { result } = renderHook(() => usePoller({ intervalMs: INTERVAL_MS })); + + act(() => { + result.current.start(pollFn); + }); + + await flushTimers(0); + + expect(pollFn).toHaveBeenCalledTimes(1); + expect(result.current.isPollingRef.current).toBe(false); + }); + + it('applies backoff and resets interval', async () => { + const pollFn = vi.fn().mockResolvedValue(true); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + const { result } = renderHook(() => + usePoller({ intervalMs: INTERVAL_MS, backoffMultiplier: 2, maxIntervalMs: 500 }) + ); + + act(() => { + result.current.start(pollFn); + result.current.applyBackoff(); + }); + + await flushTimers(0); + expect(pollFn).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy.mock.calls.some(([, delay]) => delay === INTERVAL_MS * 2)).toBe(true); + + act(() => { + result.current.resetInterval(); + }); + + await flushTimers(INTERVAL_MS * 2); + expect(pollFn).toHaveBeenCalledTimes(2); + expect(setTimeoutSpy.mock.calls.some(([, delay]) => delay === INTERVAL_MS)).toBe(true); + + setTimeoutSpy.mockRestore(); + }); + + it('stops after max duration is exceeded', async () => { + const pollFn = vi.fn().mockResolvedValue(true); + const { result } = renderHook(() => + usePoller({ intervalMs: INTERVAL_MS, maxDurationMs: MAX_DURATION_MS }) + ); + + act(() => { + result.current.start(pollFn); + }); + + await flushTimers(0); + await flushTimers(MAX_DURATION_MS + INTERVAL_MS + 1); + + expect(result.current.isPollingRef.current).toBe(false); + }); + + it('skips overlapping polls while in-flight', async () => { + let resolvePoll: (() => void) | null = null; + const pollFn = vi.fn( + () => + new Promise((resolve) => { + resolvePoll = () => resolve(true); + }) + ); + + const { result } = renderHook(() => usePoller({ intervalMs: INTERVAL_MS })); + + act(() => { + result.current.start(pollFn); + }); + + await flushTimers(0); + expect(pollFn).toHaveBeenCalledTimes(1); + + await flushTimers(INTERVAL_MS); + expect(pollFn).toHaveBeenCalledTimes(1); + + if (!resolvePoll) { + throw new Error('Poll resolver was not set'); + } + resolvePoll(); + await flushTimers(INTERVAL_MS); + expect(pollFn).toHaveBeenCalledTimes(2); + }); +}); + +describe('createPoller', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-01-01T00:00:00.000Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('runs polling and stops on false', async () => { + const pollFn = vi.fn().mockResolvedValueOnce(false); + const poller = createPoller({ intervalMs: INTERVAL_MS }); + + poller.start(pollFn); + await vi.advanceTimersByTimeAsync(0); + + expect(pollFn).toHaveBeenCalledTimes(1); + expect(poller.isPolling()).toBe(false); + }); + + it('handles backoff and reset for poller', async () => { + const pollFn = vi.fn().mockResolvedValue(true); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + const poller = createPoller({ intervalMs: INTERVAL_MS, backoffMultiplier: 2 }); + + poller.start(pollFn); + poller.applyBackoff(); + + await vi.advanceTimersByTimeAsync(0); + expect(pollFn).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy.mock.calls.some(([, delay]) => delay === INTERVAL_MS * 2)).toBe(true); + + poller.resetInterval(); + await vi.advanceTimersByTimeAsync(INTERVAL_MS * 2); + expect(pollFn).toHaveBeenCalledTimes(2); + expect(setTimeoutSpy.mock.calls.some(([, delay]) => delay === INTERVAL_MS)).toBe(true); + + setTimeoutSpy.mockRestore(); + }); + + it('stops after max duration exceeded', async () => { + const pollFn = vi.fn().mockResolvedValue(true); + const poller = createPoller({ intervalMs: INTERVAL_MS, maxDurationMs: MAX_DURATION_MS }); + + poller.start(pollFn); + await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(MAX_DURATION_MS + INTERVAL_MS + 1); + + expect(poller.isPolling()).toBe(false); + }); +}); + +describe('createInFlightGuard', () => { + it('prevents overlapping executions', async () => { + const guard = createInFlightGuard(); + let resolveFn: (() => void) | null = null; + const fn = vi.fn( + () => + new Promise((resolve) => { + resolveFn = resolve; + }) + ); + + guard.run(fn); + guard.run(fn); + expect(fn).toHaveBeenCalledTimes(1); + + if (!resolveFn) { + throw new Error('Resolver was not set'); + } + resolveFn(); + await Promise.resolve(); + + guard.run(fn); + expect(fn).toHaveBeenCalledTimes(2); + }); +}); diff --git a/client/src/lib/utils/polling.ts b/client/src/lib/utils/polling.ts index 4d7f685..e803172 100644 --- a/client/src/lib/utils/polling.ts +++ b/client/src/lib/utils/polling.ts @@ -30,7 +30,7 @@ export interface PollerOptions { */ export interface UsePollerReturn { /** Start polling with given async function. */ - start: (pollFn: () => Promise) => void; + start: (pollFn: () => Promise) => void; /** Stop polling and cancel any in-flight operations. */ stop: () => void; /** Whether polling is currently active. */ @@ -112,7 +112,7 @@ export function usePoller(options: PollerOptions): UsePollerReturn { }, [intervalMs]); const start = useCallback( - (pollFn: () => Promise) => { + (pollFn: () => Promise) => { // Stop any existing polling stop(); @@ -188,7 +188,7 @@ export function usePoller(options: PollerOptions): UsePollerReturn { * Returns a poller controller object. */ export function createPoller(options: PollerOptions): { - start: (pollFn: () => Promise) => void; + start: (pollFn: () => Promise) => void; stop: () => void; isPolling: () => boolean; applyBackoff: () => void; @@ -228,7 +228,7 @@ export function createPoller(options: PollerOptions): { currentInterval = intervalMs; }; - const start = (pollFn: () => Promise) => { + const start = (pollFn: () => Promise) => { stop(); const myGeneration = ++generation; diff --git a/client/src/pages/Analytics.test.tsx b/client/src/pages/Analytics.test.tsx new file mode 100644 index 0000000..bc6c4c2 --- /dev/null +++ b/client/src/pages/Analytics.test.tsx @@ -0,0 +1,215 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import AnalyticsPage from '@/pages/Analytics'; +import type { AnalyticsOverview, EntityAnalytics, ListSpeakerStatsResponse } from '@/api/types/features/analytics'; + +const MOCK_TOTAL_MEETINGS = 2; +const MOCK_TOTAL_DURATION = 600; +const MOCK_TOTAL_WORDS = 1200; +const MOCK_TOTAL_SEGMENTS = 42; +const MOCK_SPEAKER_COUNT = 2; +const MOCK_SPEAKER_TIME = 300; +const MOCK_SPEAKER_SEGMENTS = 12; +const MOCK_SPEAKER_MEETINGS = 1; +const MOCK_SPEAKER_CONFIDENCE = 0.9; +const MOCK_ENTITY_COUNT = 1; +const MOCK_ENTITY_MENTIONS = 3; +const MOCK_ENTITY_MEETING_COUNT = 1; + +const MOCK_OVERVIEW: AnalyticsOverview = { + daily: [], + total_meetings: MOCK_TOTAL_MEETINGS, + total_duration: MOCK_TOTAL_DURATION, + total_words: MOCK_TOTAL_WORDS, + total_segments: MOCK_TOTAL_SEGMENTS, + speaker_count: MOCK_SPEAKER_COUNT, +}; + +const MOCK_SPEAKER_RESPONSE: ListSpeakerStatsResponse = { + speakers: [ + { + speaker_id: 'speaker-1', + display_name: 'Alex', + total_time: MOCK_SPEAKER_TIME, + segment_count: MOCK_SPEAKER_SEGMENTS, + meeting_count: MOCK_SPEAKER_MEETINGS, + avg_confidence: MOCK_SPEAKER_CONFIDENCE, + }, + ], +}; + +const MOCK_ENTITY_ANALYTICS: EntityAnalytics = { + by_category: [ + { + category: 'person', + count: MOCK_ENTITY_COUNT, + total_mentions: MOCK_ENTITY_MENTIONS, + }, + ], + top_entities: [ + { + text: 'NoteFlow', + category: 'product', + mention_count: MOCK_ENTITY_MENTIONS, + meeting_count: MOCK_ENTITY_MEETING_COUNT, + }, + ], + total_entities: MOCK_ENTITY_COUNT, + total_mentions: MOCK_ENTITY_MENTIONS, +}; + +const MOCK_MEETINGS_RESPONSE = { meetings: [], total_count: MOCK_TOTAL_MEETINGS }; + +const queryState = { + overviewData: undefined as AnalyticsOverview | undefined, + overviewLoading: false, + speakerData: undefined as ListSpeakerStatsResponse | undefined, + speakersLoading: false, + entityAnalyticsData: undefined as EntityAnalytics | undefined, + entityLoading: false, + meetingsData: MOCK_MEETINGS_RESPONSE, +}; + +type QueryOptions = { queryKey: unknown; enabled?: boolean }; +type QueryResult = { data: unknown; isLoading: boolean }; + +const { mockUseQuery } = vi.hoisted(() => ({ + mockUseQuery: vi.fn<(options: QueryOptions) => QueryResult>((options) => { + const { queryKey, enabled } = options; + if (enabled === false) { + return { data: undefined, isLoading: false }; + } + const key = Array.isArray(queryKey) ? queryKey[0] : queryKey; + if (typeof key !== 'string') { + return { data: undefined, isLoading: false }; + } + if (key === 'analytics-overview') { + return { data: queryState.overviewData, isLoading: queryState.overviewLoading }; + } + if (key === 'speaker-stats') { + return { data: queryState.speakerData, isLoading: queryState.speakersLoading }; + } + if (key === 'meetings') { + return { data: queryState.meetingsData, isLoading: false }; + } + if (key === 'entity-analytics') { + return { data: queryState.entityAnalyticsData, isLoading: queryState.entityLoading }; + } + return { data: undefined, isLoading: false }; + }), +})); + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useQuery: mockUseQuery, + }; +}); + +vi.mock('@/components/features/analytics/meetings-tab', () => ({ + MeetingsTab: () =>
MeetingsTab content
, +})); + +vi.mock('@/components/features/analytics/speech-analysis-tab', () => ({ + SpeechAnalysisTab: () =>
SpeechAnalysisTab content
, +})); + +vi.mock('@/components/features/analytics/entities-tab', () => ({ + EntitiesTab: () =>
EntitiesTab content
, +})); + +vi.mock('@/components/features/analytics/performance-tab', () => ({ + PerformanceTab: () =>
PerformanceTab content
, +})); + +vi.mock('@/components/features/analytics/logs-tab', () => ({ + LogsTab: () =>
LogsTab content
, +})); + +vi.mock('@/components/ui/tabs', async () => { + const React = await import('react'); + + type TabsContextValue = { + value: string; + onValueChange: (value: string) => void; + }; + + const TabsContext = React.createContext(null); + + return { + Tabs: ({ + value, + onValueChange, + children, + }: { + value: string; + onValueChange: (value: string) => void; + children: React.ReactNode; + }) => ( + +
{children}
+
+ ), + TabsList: ({ children }: { children: React.ReactNode }) =>
{children}
, + TabsTrigger: ({ value, children }: { value: string; children: React.ReactNode }) => { + const context = React.useContext(TabsContext); + const isActive = context?.value === value; + return ( + + ); + }, + TabsContent: ({ value, children }: { value: string; children: React.ReactNode }) => { + const context = React.useContext(TabsContext); + if (context?.value !== value) { + return null; + } + return
{children}
; + }, + }; +}); + +describe('AnalyticsPage', () => { + beforeEach(() => { + queryState.overviewData = MOCK_OVERVIEW; + queryState.overviewLoading = false; + queryState.speakerData = MOCK_SPEAKER_RESPONSE; + queryState.speakersLoading = false; + queryState.entityAnalyticsData = MOCK_ENTITY_ANALYTICS; + queryState.entityLoading = false; + queryState.meetingsData = MOCK_MEETINGS_RESPONSE; + }); + + it('shows loading state for meetings tab', () => { + queryState.overviewLoading = true; + queryState.overviewData = undefined; + + render(); + + expect(document.querySelector('.animate-pulse')).toBeInTheDocument(); + }); + + it('renders meetings tab content when data is loaded', () => { + render(); + + expect(screen.getByText('MeetingsTab content')).toBeInTheDocument(); + }); + + it('switches to entities tab content', async () => { + render(); + + const entitiesTab = screen.getByRole('tab', { name: /entities/i }); + fireEvent.pointerDown(entitiesTab); + fireEvent.click(entitiesTab); + + expect(await screen.findByText('EntitiesTab content')).toBeInTheDocument(); + }); +}); diff --git a/client/src/pages/Analytics.tsx b/client/src/pages/Analytics.tsx index 8b27321..62a4521 100644 --- a/client/src/pages/Analytics.tsx +++ b/client/src/pages/Analytics.tsx @@ -1,374 +1,19 @@ -// Analytics page - Meeting insights and statistics - import { useQuery } from '@tanstack/react-query'; -import { format, subDays } from 'date-fns'; -import { - Activity, - BarChart3, - Calendar, - Clock, - FileText, - MessageSquare, - Mic, - ScrollText, - TrendingUp, - Users, -} from 'lucide-react'; -import { useId, useMemo, useState } from 'react'; -import { - Area, - AreaChart, - Bar, - BarChart, - CartesianGrid, - Cell, - Legend, - Pie, - PieChart, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from 'recharts'; +import { subDays } from 'date-fns'; +import { Activity, BarChart3, MessageSquare, ScrollText, Tag } from 'lucide-react'; +import { useMemo, useState } from 'react'; import { getAPI } from '@/api/interface'; -import type { AnalyticsOverview, ListMeetingsResponse, SpeakerStat } from '@/api/types'; -import { - SPEAKER_COLORS, - SPEAKER_COLOR_CLASSES, - speakerLabel, - wordCountTickLabel, -} from '@/components/features/analytics/analytics-utils'; +import type { ListMeetingsResponse } from '@/api/types'; +import { EntitiesTab } from '@/components/features/analytics/entities-tab'; import { LogsTab } from '@/components/features/analytics/logs-tab'; +import { MeetingsTab } from '@/components/features/analytics/meetings-tab'; import { PerformanceTab } from '@/components/features/analytics/performance-tab'; import { SpeechAnalysisTab } from '@/components/features/analytics/speech-analysis-tab'; -import { StatsCard } from '@/components/common'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; +import { mapSpeakerStats } from '@/components/features/analytics/analytics-utils'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { formatDuration } from '@/lib/utils/format'; -import { - chartAxis, - chartHeight, - chartStrokes, - flexLayout, - overflow, - typography, -} from '@/lib/ui/styles'; -import { cn } from '@/lib/utils'; -const titleRowClass = flexLayout.itemsGap2; const ANALYTICS_DAYS = 14; -interface DailyStats { - date: string; - dateLabel: string; - meetings: number; - totalDuration: number; - wordCount: number; -} - -interface SpeakerStats { - speakerId: string; - displayName: string; - totalTime: number; - percentage: number; - segmentCount: number; - meetingCount: number; -} - -function transformDailyStats(overview: AnalyticsOverview): DailyStats[] { - return overview.daily.map((d) => ({ - date: d.date, - dateLabel: format(new Date(d.date), 'MMM d'), - meetings: d.meetings, - totalDuration: d.total_duration, - wordCount: d.word_count, - })); -} - -function transformSpeakerStats(speakers: SpeakerStat[]): SpeakerStats[] { - const totalTime = speakers.reduce((sum, s) => sum + s.total_time, 0); - return speakers.map((s) => ({ - speakerId: s.speaker_id, - displayName: s.display_name, - totalTime: s.total_time, - percentage: totalTime > 0 ? (s.total_time / totalTime) * 100 : 0, - segmentCount: s.segment_count, - meetingCount: s.meeting_count, - })); -} - -function MeetingsAnalyticsContent({ - overview, - speakerStats, - chartConfig, -}: { - overview: AnalyticsOverview; - speakerStats: SpeakerStats[]; - chartConfig: Record; -}) { - const chartId = useId(); - const gridProps = { strokeDasharray: '3 3', className: chartStrokes.muted }; - const durationTooltip = ( - formatDuration(Number(value))} /> - ); - const defaultTooltip = ; - const wordsTooltip = ( - `${Number(value).toLocaleString()} words`} /> - ); - - const dailyTrends = transformDailyStats(overview); - const avgDuration = - overview.total_meetings > 0 ? overview.total_duration / overview.total_meetings : 0; - const avgWordsPerMeeting = - overview.total_meetings > 0 ? overview.total_words / overview.total_meetings : 0; - - return ( -
-
- - - - -
- -
- - - - - Meeting Duration Trends - - Daily meeting duration over the last {ANALYTICS_DAYS} days - - -
- - - - - - - - - - - `${Math.round(v / 60)}m`} - /> - - - - -
-
-
- - - - - - Meetings Per Day - - Number of meetings recorded each day - - -
- - - - - - - - - -
-
-
-
- -
- - - - - Speaker Participation - - Speaking time distribution across all meetings - - -
- {speakerStats.length > 0 ? ( - - - - {speakerStats.map((stat, idx) => ( - - ))} - - [`${value.toFixed(1)}%`, name]} - /> - - - - ) : ( -

No speaker data available

- )} -
-
-
- - - - - - Word Count Trends - - Words transcribed per day - - -
- - - - - - - - - - - - - - - -
-
-
-
- - {speakerStats.length > 0 && ( - - - Speaker Breakdown - Detailed speaking time by speaker - - -
- {speakerStats.map((speaker, index) => { - const speakerColorClass = - SPEAKER_COLOR_CLASSES[index % SPEAKER_COLOR_CLASSES.length]; - const barWidth = `${speaker.percentage}%`; - - return ( -
-
-
-
- - {speaker.displayName} - - - {formatDuration(speaker.totalTime)} ({speaker.percentage.toFixed(1)}%) - -
-
-
-
-
-
- ); - })} -
- - - )} -
- ); -} - export default function AnalyticsPage() { const [activeTab, setActiveTab] = useState('meetings'); @@ -394,16 +39,26 @@ export default function AnalyticsPage() { }); const speakerStats = useMemo( - () => (speakerResponse?.speakers ? transformSpeakerStats(speakerResponse.speakers) : []), + () => (speakerResponse?.speakers ? mapSpeakerStats(speakerResponse.speakers) : []), [speakerResponse] ); const { data: meetingsData } = useQuery({ - queryKey: ['meetings', 'all'], - queryFn: () => getAPI().listMeetings({ limit: 100 }), + queryKey: ['meetings', 'speech-analysis'], + queryFn: () => getAPI().listMeetings({ limit: 100, include_segments: true }), enabled: activeTab === 'speech', }); + const { data: entityAnalytics, isLoading: entitiesLoading } = useQuery({ + queryKey: ['entity-analytics', startTime, endTime], + queryFn: () => + getAPI().getEntityAnalytics({ + start_time: startTime, + end_time: endTime, + }), + enabled: activeTab === 'entities', + }); + const chartConfig = { meetings: { label: 'Meetings', color: 'hsl(var(--chart-1))' }, duration: { label: 'Duration (min)', color: 'hsl(var(--chart-2))' }, @@ -423,7 +78,7 @@ export default function AnalyticsPage() {
- + Meetings @@ -432,6 +87,10 @@ export default function AnalyticsPage() { Speech + + + Entities + Performance @@ -452,7 +111,7 @@ export default function AnalyticsPage() {
) : ( - + + {entitiesLoading || !entityAnalytics ? ( +
+
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+
+ ) : ( + + )} + diff --git a/client/src/pages/Home.test.tsx b/client/src/pages/Home.test.tsx new file mode 100644 index 0000000..3dd8b29 --- /dev/null +++ b/client/src/pages/Home.test.tsx @@ -0,0 +1,201 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ConnectionProvider } from '@/contexts/connection-context'; +import { ProjectProvider } from '@/contexts/project-context'; +import { WorkspaceProvider } from '@/contexts/workspace-context'; +import HomePage from '@/pages/Home'; + +const MOCK_DURATION_SECONDS = 3600; +const MOCK_CREATED_AT_EPOCH = Math.floor(Date.now() / 1000); + +function createMockMeeting(overrides: Partial<{ + id: string; + title: string; + state: string; +}>) { + return { + id: overrides.id ?? 'meeting-1', + title: overrides.title ?? 'Untitled Meeting', + state: overrides.state ?? 'completed', + created_at: MOCK_CREATED_AT_EPOCH, + duration_seconds: MOCK_DURATION_SECONDS, + segments: [{ id: 'seg-1', text: 'Sample transcript text' }], + metadata: {}, + }; +} + +const mockListMeetings = vi.fn(); +const mockListWorkspaces = vi.fn(); +const mockListProjects = vi.fn(); +const mockGetActiveProject = vi.fn(); +const mockSetActiveProject = vi.fn(); + +vi.mock('@/api/interface', () => ({ + getAPI: () => ({ + listMeetings: mockListMeetings, + listWorkspaces: mockListWorkspaces, + listProjects: mockListProjects, + getActiveProject: mockGetActiveProject, + setActiveProject: mockSetActiveProject, + connect: vi.fn().mockResolvedValue({ version: '1.0.0' }), + }), + setAPIInstance: vi.fn(), +})); + +vi.mock('@/contexts/connection-state', async () => { + const React = await import('react'); + const ConnectionContext = React.createContext(null); + return { + ConnectionContext, + useConnectionState: () => ({ + state: { mode: 'connected', disconnectedAt: null, reconnectAttempts: 0 }, + mode: 'connected', + isConnected: true, + isReadOnly: false, + isReconnecting: false, + isSimulating: false, + }), + }; +}); + +vi.mock('@/lib/preferences', () => ({ + preferences: { + get: () => ({ simulate_transcription: false }), + subscribe: () => () => {}, + isTaskCompleted: () => false, + toggleTaskCompletion: vi.fn(), + }, +})); + +function createWrapper() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + return function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + + + + + + ); + }; +} + +describe('HomePage', () => { + beforeEach(() => { + vi.clearAllMocks(); + localStorage.clear(); + mockListWorkspaces.mockResolvedValue({ workspaces: [] }); + mockListProjects.mockResolvedValue({ projects: [], total_count: 0 }); + mockGetActiveProject.mockResolvedValue({ project_id: '' }); + mockSetActiveProject.mockResolvedValue(undefined); + }); + + it('renders greeting text', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText(/what's happening with your meetings/i)).toBeInTheDocument(); + }); + }); + + it('shows empty state when no meetings', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('No meetings yet')).toBeInTheDocument(); + }); + }); + + it('renders recently recorded section header', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + expect(screen.getByText('Recently Recorded')).toBeInTheDocument(); + }); + + it('renders action items section header', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + expect(screen.getByText('Action Items')).toBeInTheDocument(); + }); + + it('shows empty tasks state when no tasks', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('No pending tasks')).toBeInTheDocument(); + }); + }); + + it('renders meeting cards when meetings exist', async () => { + mockListMeetings.mockResolvedValue({ + meetings: [ + createMockMeeting({ id: 'meeting-1', title: 'Team Standup', state: 'completed' }), + createMockMeeting({ id: 'meeting-2', title: 'Product Review', state: 'completed' }), + ], + }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Team Standup')).toBeInTheDocument(); + expect(screen.getByText('Product Review')).toBeInTheDocument(); + }); + }); + + it('shows active recording card when meeting is recording', async () => { + mockListMeetings.mockResolvedValue({ + meetings: [ + createMockMeeting({ id: 'meeting-1', title: 'Live Call', state: 'recording' }), + ], + }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Live Call')).toBeInTheDocument(); + }); + expect(screen.getByText('Recording in progress')).toBeInTheDocument(); + }); + + it('has view all link for meetings', async () => { + mockListMeetings.mockResolvedValue({ meetings: [] }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + const viewAllButtons = screen.getAllByRole('link', { name: /view all/i }); + expect(viewAllButtons.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/client/src/pages/MeetingDetail.test.tsx b/client/src/pages/MeetingDetail.test.tsx new file mode 100644 index 0000000..28d7cb7 --- /dev/null +++ b/client/src/pages/MeetingDetail.test.tsx @@ -0,0 +1,284 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ConnectionProvider } from '@/contexts/connection-context'; +import { ProjectProvider } from '@/contexts/project-context'; +import { WorkspaceProvider } from '@/contexts/workspace-context'; +import MeetingDetailPage from '@/pages/MeetingDetail'; + +const MOCK_MEETING_ID = 'meeting-123'; +const MOCK_DURATION_SECONDS = 3600; +const MOCK_CREATED_AT_EPOCH = Math.floor(Date.now() / 1000); + +function createMockMeeting(overrides: Partial<{ + id: string; + title: string; + state: string; +}> = {}) { + return { + id: overrides.id ?? MOCK_MEETING_ID, + title: overrides.title ?? 'Test Meeting', + state: overrides.state ?? 'completed', + created_at: MOCK_CREATED_AT_EPOCH, + duration_seconds: MOCK_DURATION_SECONDS, + segments: [ + { + segment_id: 'seg-1', + text: 'First segment of the transcript', + start_time: 0, + end_time: 10, + speaker_id: 'speaker-1', + }, + { + segment_id: 'seg-2', + text: 'Second segment of the transcript', + start_time: 10, + end_time: 20, + speaker_id: 'speaker-2', + }, + ], + metadata: {}, + }; +} + +const mockGetMeeting = vi.fn(); +const mockListMeetings = vi.fn(); +const mockListWorkspaces = vi.fn(); +const mockListProjects = vi.fn(); +const mockGetActiveProject = vi.fn(); +const mockSetActiveProject = vi.fn(); +const mockExportTranscript = vi.fn(); +const mockGenerateSummary = vi.fn(); +const mockExtractEntities = vi.fn(); + +vi.mock('@/api/interface', () => ({ + getAPI: () => ({ + getMeeting: mockGetMeeting, + listMeetings: mockListMeetings, + listWorkspaces: mockListWorkspaces, + listProjects: mockListProjects, + getActiveProject: mockGetActiveProject, + setActiveProject: mockSetActiveProject, + exportTranscript: mockExportTranscript, + generateSummary: mockGenerateSummary, + extractEntities: mockExtractEntities, + connect: vi.fn().mockResolvedValue({ version: '1.0.0' }), + }), + setAPIInstance: vi.fn(), +})); + +vi.mock('@/api', () => ({ + isTauriEnvironment: () => false, + IdentityDefaults: { + DEFAULT_USER_ID: '00000000-0000-0000-0000-000000000001', + DEFAULT_USER_NAME: 'Local User', + DEFAULT_WORKSPACE_ID: '00000000-0000-0000-0000-000000000001', + DEFAULT_WORKSPACE_NAME: 'Personal', + DEFAULT_PROJECT_ID: '00000000-0000-0000-0000-000000000002', + DEFAULT_PROJECT_NAME: 'General', + DEFAULT_ROLE: 'owner', + }, + getConnectionState: () => ({ mode: 'connected', disconnectedAt: null, reconnectAttempts: 0 }), + subscribeConnectionState: () => () => {}, + setConnectionMode: vi.fn(), + setConnectionServerUrl: vi.fn(), + TauriEvents: { CONNECTION_CHANGE: 'connection-change' }, + extractErrorMessage: (error: unknown) => error instanceof Error ? error.message : String(error), +})); + +vi.mock('@/contexts/connection-state', async () => { + const React = await import('react'); + const ConnectionContext = React.createContext(null); + return { + ConnectionContext, + useConnectionState: () => ({ + state: { mode: 'connected', disconnectedAt: null, reconnectAttempts: 0 }, + mode: 'connected', + isConnected: true, + isReadOnly: false, + isReconnecting: false, + isSimulating: false, + }), + }; +}); + +vi.mock('@/lib/preferences', () => ({ + preferences: { + get: () => ({ simulate_transcription: false, speaker_names: [] }), + subscribe: () => () => {}, + getSpeakerName: () => null, + }, +})); + +vi.mock('@/hooks', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useGuardedMutation: () => ({ + guard: async function guard(action: () => Promise) { + return action(); + }, + isReadOnly: false, + mode: 'connected', + }), + useEntityExtraction: () => ({ + state: { status: 'idle', entities: [], cached: false, error: null }, + extract: vi.fn(), + clearEntities: vi.fn(), + isExtracting: false, + }), + usePostProcessing: () => ({ + state: { + meetingId: null, + summary: { status: 'idle', error: null }, + entities: { status: 'idle', error: null }, + diarization: { status: 'idle', error: null }, + overallStatus: 'idle', + isActive: false, + }, + start: vi.fn(), + shouldAutoStart: () => false, + }), + }; +}); + +vi.mock('./meeting-detail/use-playback', () => ({ + usePlayback: () => ({ + playback: { isPlaying: false, currentTime: 0 }, + seekPosition: 0, + setSeekPosition: vi.fn(), + handlePlay: vi.fn(), + handlePause: vi.fn(), + handleStop: vi.fn(), + handleSeek: vi.fn(), + handleSegmentSelect: vi.fn(), + }), +})); + +function createWrapper(meetingId: string = MOCK_MEETING_ID) { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + return function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + + + + + + ); + }; +} + +describe('MeetingDetailPage', () => { + beforeEach(() => { + vi.clearAllMocks(); + localStorage.clear(); + mockListWorkspaces.mockResolvedValue({ workspaces: [] }); + mockListProjects.mockResolvedValue({ projects: [], total_count: 0 }); + mockGetActiveProject.mockResolvedValue({ project_id: '' }); + mockSetActiveProject.mockResolvedValue(undefined); + mockListMeetings.mockResolvedValue({ meetings: [], total_count: 0 }); + mockExtractEntities.mockResolvedValue({ entities: [] }); + }); + + it('shows loading skeleton while fetching', async () => { + let resolvePromise: ((value: unknown) => void) | undefined; + const promise = new Promise((resolve) => { + resolvePromise = resolve; + }); + mockGetMeeting.mockReturnValue(promise); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(document.querySelector('.skeleton-shimmer')).toBeInTheDocument(); + }); + + if (resolvePromise) { + resolvePromise(createMockMeeting()); + } + }); + + it('shows not found message when meeting does not exist', async () => { + mockGetMeeting.mockResolvedValue(null); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Meeting not found')).toBeInTheDocument(); + }); + }); + + it('renders meeting title when loaded', async () => { + mockGetMeeting.mockResolvedValue(createMockMeeting({ title: 'Team Standup' })); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Team Standup')).toBeInTheDocument(); + }); + }); + + it('renders transcript section', async () => { + mockGetMeeting.mockResolvedValue(createMockMeeting()); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Transcript')).toBeInTheDocument(); + }); + }); + + it('renders summary section', async () => { + mockGetMeeting.mockResolvedValue(createMockMeeting()); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Test Meeting')).toBeInTheDocument(); + }); + + const summaryButtons = screen.getAllByRole('button', { name: /summary/i }); + expect(summaryButtons.length).toBeGreaterThanOrEqual(1); + }); + + it('renders entities tab', async () => { + mockGetMeeting.mockResolvedValue(createMockMeeting()); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByRole('tab', { name: /entities/i })).toBeInTheDocument(); + }); + }); + + it('renders notes tab', async () => { + mockGetMeeting.mockResolvedValue(createMockMeeting()); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByRole('tab', { name: /notes/i })).toBeInTheDocument(); + }); + }); +}); diff --git a/client/src/pages/Meetings.test.tsx b/client/src/pages/Meetings.test.tsx new file mode 100644 index 0000000..efa1eef --- /dev/null +++ b/client/src/pages/Meetings.test.tsx @@ -0,0 +1,253 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ConnectionProvider } from '@/contexts/connection-context'; +import { ProjectProvider } from '@/contexts/project-context'; +import { WorkspaceProvider } from '@/contexts/workspace-context'; +import MeetingsPage from '@/pages/Meetings'; + +const MOCK_SEGMENT_COUNT = 5; +const MOCK_WORD_COUNT = 100; +const MOCK_TOTAL_COUNT_SINGLE = 1; +const MOCK_TOTAL_COUNT_MULTIPLE = 2; +const MOCK_TOTAL_COUNT_PAGINATION = 25; +const MOCK_DURATION_SECONDS = 3600; +const MOCK_CREATED_AT_EPOCH = Math.floor(Date.now() / 1000); + +function createMockMeeting(overrides: Partial<{ + id: string; + title: string; + state: string; + created_at: number; + segment_count: number; + word_count: number; +}>) { + return { + id: overrides.id ?? 'meeting-1', + title: overrides.title ?? 'Untitled Meeting', + state: overrides.state ?? 'completed', + created_at: overrides.created_at ?? MOCK_CREATED_AT_EPOCH, + duration_seconds: MOCK_DURATION_SECONDS, + segments: [{ id: 'seg-1', text: 'Sample transcript text' }], + metadata: {}, + segment_count: overrides.segment_count ?? MOCK_SEGMENT_COUNT, + word_count: overrides.word_count ?? MOCK_WORD_COUNT, + }; +} + +const mockListMeetings = vi.fn(); +const mockDeleteMeeting = vi.fn(); +const mockListWorkspaces = vi.fn(); +const mockListProjects = vi.fn(); +const mockGetActiveProject = vi.fn(); +const mockSetActiveProject = vi.fn(); + +vi.mock('@/api/interface', () => ({ + getAPI: () => ({ + listMeetings: mockListMeetings, + deleteMeeting: mockDeleteMeeting, + listWorkspaces: mockListWorkspaces, + listProjects: mockListProjects, + getActiveProject: mockGetActiveProject, + setActiveProject: mockSetActiveProject, + connect: vi.fn().mockResolvedValue({ version: '1.0.0' }), + }), + setAPIInstance: vi.fn(), +})); + +vi.mock('@/contexts/connection-state', async () => { + const React = await import('react'); + const ConnectionContext = React.createContext(null); + return { + ConnectionContext, + useConnectionState: () => ({ + state: { mode: 'connected', disconnectedAt: null, reconnectAttempts: 0 }, + mode: 'connected', + isConnected: true, + isReadOnly: false, + isReconnecting: false, + isSimulating: false, + }), + }; +}); + +function createWrapper() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + return function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + + + + + + + ); + }; +} + +describe('MeetingsPage', () => { + beforeEach(() => { + vi.clearAllMocks(); + localStorage.clear(); + mockListWorkspaces.mockResolvedValue({ workspaces: [] }); + mockListProjects.mockResolvedValue({ projects: [], total_count: 0 }); + mockGetActiveProject.mockResolvedValue({ project_id: '' }); + mockSetActiveProject.mockResolvedValue(undefined); + }); + + it('renders page title', async () => { + mockListMeetings.mockResolvedValue({ meetings: [], total_count: 0 }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + expect(screen.getByText('Meetings')).toBeInTheDocument(); + }); + + it('shows loading skeletons while fetching', async () => { + let resolvePromise: ((value: unknown) => void) | undefined; + const promise = new Promise((resolve) => { + resolvePromise = resolve; + }); + mockListMeetings.mockReturnValue(promise); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + expect(screen.getByText('Past Recordings')).toBeInTheDocument(); + + if (resolvePromise) { + resolvePromise({ meetings: [], total_count: 0 }); + } + }); + + it('shows empty state when no meetings', async () => { + mockListMeetings.mockResolvedValue({ meetings: [], total_count: 0 }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('No meetings found')).toBeInTheDocument(); + }); + }); + + it('renders meeting cards when meetings exist', async () => { + mockListMeetings.mockResolvedValue({ + meetings: [ + createMockMeeting({ + id: 'meeting-1', + title: 'Team Standup', + state: 'completed', + segment_count: MOCK_SEGMENT_COUNT, + word_count: MOCK_WORD_COUNT, + }), + ], + total_count: MOCK_TOTAL_COUNT_SINGLE, + }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Team Standup')).toBeInTheDocument(); + }); + }); + + it('filters meetings by search query', async () => { + mockListMeetings.mockResolvedValue({ + meetings: [ + createMockMeeting({ + id: 'meeting-1', + title: 'Team Standup', + state: 'completed', + }), + createMockMeeting({ + id: 'meeting-2', + title: 'Product Review', + state: 'completed', + }), + ], + total_count: MOCK_TOTAL_COUNT_MULTIPLE, + }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText('Team Standup')).toBeInTheDocument(); + }); + + const searchInput = screen.getByPlaceholderText('Search meetings...'); + fireEvent.change(searchInput, { target: { value: 'Product' } }); + + await waitFor(() => { + expect(screen.queryByText('Team Standup')).not.toBeInTheDocument(); + }); + expect(screen.getByText('Product Review')).toBeInTheDocument(); + }); + + it('shows Load More button when more meetings exist', async () => { + mockListMeetings.mockResolvedValue({ + meetings: [ + createMockMeeting({ + id: 'meeting-1', + title: 'Meeting 1', + state: 'completed', + }), + ], + total_count: MOCK_TOTAL_COUNT_PAGINATION, + }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + expect(screen.getByText(/Load More/)).toBeInTheDocument(); + }); + }); + + it('shows filter buttons for meeting states', async () => { + mockListMeetings.mockResolvedValue({ meetings: [], total_count: 0 }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + await waitFor(() => { + const stateFilterButtons = screen.getAllByRole('button', { name: /^(all|completed|stopped|recording)$/i }); + expect(stateFilterButtons.length).toBeGreaterThanOrEqual(4); + }); + }); + + it('changes state filter when clicking filter button', async () => { + mockListMeetings.mockResolvedValue({ meetings: [], total_count: 0 }); + + const Wrapper = createWrapper(); + render(, { wrapper: Wrapper }); + + const completedButton = screen.getByRole('button', { name: /completed/i }); + fireEvent.click(completedButton); + + await waitFor(() => { + expect(mockListMeetings).toHaveBeenCalledWith( + expect.objectContaining({ + states: ['completed'], + }) + ); + }); + }); +}); diff --git a/client/src/pages/Meetings.tsx b/client/src/pages/Meetings.tsx index 519f044..e01dac4 100644 --- a/client/src/pages/Meetings.tsx +++ b/client/src/pages/Meetings.tsx @@ -1,10 +1,10 @@ // Meetings list page -import { Calendar } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; +import { Calendar, Loader2 } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { getAPI } from '@/api/interface'; -import type { MeetingState } from '@/api/types'; +import type { Meeting, MeetingState } from '@/api/types'; import type { ProjectScope } from '@/api/types/requests'; import { EmptyState } from '@/components/common'; import { MeetingCard } from '@/components/features/meetings'; @@ -15,7 +15,6 @@ import { SearchIcon } from '@/components/ui/search-icon'; import { SkeletonMeetingCard } from '@/components/ui/skeleton'; import { UpcomingMeetings } from '@/components/features/calendar'; import { useProjects } from '@/contexts/project-state'; -import { useAsyncData } from '@/hooks'; import { useGuardedMutation } from '@/hooks'; import { addClientLog } from '@/lib/observability/client'; import { preferences } from '@/lib/preferences'; @@ -23,6 +22,7 @@ import { MEETINGS_PAGE_LIMIT, SKELETON_CARDS_COUNT } from '@/lib/constants/timin const FILTER_STATES = ['all', 'completed', 'stopped', 'recording'] as const; type FilterState = (typeof FILTER_STATES)[number]; + export default function MeetingsPage() { const { projectId } = useParams<{ projectId: string }>(); const { activeProject, projects, isLoading: projectsLoading } = useProjects(); @@ -41,41 +41,64 @@ export default function MeetingsPage() { [projects] ); - // Skip fetching when no valid project context + const [meetings, setMeetings] = useState([]); + const [totalCount, setTotalCount] = useState(0); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const shouldSkipFetch = (projectScope === 'selected' && selectedProjectIds.length === 0) || (projectScope === 'active' && !resolvedProjectId && !projectsLoading); - const { - data: meetings = [], - isLoading: loading, - refetch, - } = useAsyncData( - () => - getAPI() - .listMeetings({ + const fetchMeetings = useCallback( + async (offset: number, append: boolean) => { + if (shouldSkipFetch) { + setMeetings([]); + setTotalCount(0); + setLoading(false); + return; + } + + if (append) { + setLoadingMore(true); + } else { + setLoading(true); + } + + try { + const response = await getAPI().listMeetings({ limit: MEETINGS_PAGE_LIMIT, + offset, states: stateFilter === 'all' ? undefined : [stateFilter as MeetingState], project_id: projectScope === 'active' ? resolvedProjectId : undefined, project_ids: projectScope === 'selected' ? selectedProjectIds : undefined, - }) - .then((r) => r.meetings), - [stateFilter, resolvedProjectId, projectScope, selectedProjectIds], - { - initialData: [], - skip: shouldSkipFetch, - onError: (error) => { + }); + if (append) { + setMeetings((prev) => [...prev, ...response.meetings]); + } else { + setMeetings(response.meetings); + } + setTotalCount(response.total_count); + } catch (error) { addClientLog({ level: 'warning', source: 'app', message: 'Failed to fetch meeting list', - details: error, + details: error instanceof Error ? error.message : String(error), metadata: { context: 'meetings_list_fetch' }, }); - }, - } + } finally { + setLoading(false); + setLoadingMore(false); + } + }, + [shouldSkipFetch, stateFilter, projectScope, resolvedProjectId, selectedProjectIds] ); + useEffect(() => { + fetchMeetings(0, false); + }, [fetchMeetings]); + useEffect(() => { preferences.setMeetingsProjectFilter(projectScope, selectedProjectIds); }, [projectScope, selectedProjectIds]); @@ -84,6 +107,13 @@ export default function MeetingsPage() { () => meetings.filter((m) => m.title.toLowerCase().includes(searchQuery.toLowerCase())), [meetings, searchQuery] ); + + const hasMore = meetings.length < totalCount; + + const handleLoadMore = () => { + fetchMeetings(meetings.length, true); + }; + const handleDelete = async (meetingId: string, e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -93,7 +123,7 @@ export default function MeetingsPage() { message: 'Deleting meetings requires an active server connection.', }); if (result) { - refetch(); + fetchMeetings(0, false); } } }; @@ -166,17 +196,38 @@ export default function MeetingsPage() { } /> ) : ( -
- {filteredMeetings.map((meeting, i) => ( - - ))} -
+ <> +
+ {filteredMeetings.map((meeting, i) => ( + + ))} +
+ {hasMore && !searchQuery && ( +
+ +
+ )} + )}
diff --git a/client/src/pages/NotFound.test.tsx b/client/src/pages/NotFound.test.tsx new file mode 100644 index 0000000..15540d9 --- /dev/null +++ b/client/src/pages/NotFound.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import NotFound from '@/pages/NotFound'; + +describe('NotFound', () => { + it('renders 404 heading', () => { + render(); + + expect(screen.getByRole('heading', { name: '404' })).toBeInTheDocument(); + }); + + it('renders error message', () => { + render(); + + expect(screen.getByText('Oops! Page not found')).toBeInTheDocument(); + }); + + it('renders link to home page', () => { + render(); + + const link = screen.getByRole('link', { name: /return to home/i }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', '/'); + }); +}); diff --git a/client/src/pages/People.tsx b/client/src/pages/People.tsx index 55ee3ed..ce174bd 100644 --- a/client/src/pages/People.tsx +++ b/client/src/pages/People.tsx @@ -198,6 +198,7 @@ export default function PeoplePage() { if (!speakerStatsResponse?.speakers) { return []; } + void prefsVersion; return speakerStatsResponse.speakers.map((stat) => ({ ...stat, displayName: preferences.getGlobalSpeakerName(stat.speaker_id) || stat.display_name || stat.speaker_id, diff --git a/client/src/pages/Projects.test.tsx b/client/src/pages/Projects.test.tsx new file mode 100644 index 0000000..673b1f2 --- /dev/null +++ b/client/src/pages/Projects.test.tsx @@ -0,0 +1,16 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import ProjectsPage from '@/pages/Projects'; + +vi.mock('@/components/features/projects/ProjectList', () => ({ + ProjectList: () =>
ProjectList content
, +})); + +describe('ProjectsPage', () => { + it('renders project list', () => { + render(); + + expect(screen.getByText('ProjectList content')).toBeInTheDocument(); + }); +}); diff --git a/client/src/pages/Tasks.test.tsx b/client/src/pages/Tasks.test.tsx new file mode 100644 index 0000000..360c4d5 --- /dev/null +++ b/client/src/pages/Tasks.test.tsx @@ -0,0 +1,154 @@ +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import TasksPage from '@/pages/Tasks'; +import type { ListTasksResponse, Task, TaskWithMeeting } from '@/api/types/features/tasks'; +import type { Project } from '@/api/types/projects'; + +const MOCK_TASK_ID = 'task-1'; +const MOCK_MEETING_ID = 'meeting-1'; +const MOCK_PROJECT_ID = 'project-1'; +const MOCK_WORKSPACE_ID = 'workspace-1'; +const MOCK_TASK_PRIORITY = 3; +const MOCK_TASK_CREATED_AT = 1700000000; +const MOCK_TASK_TOTAL = 1; + +const MOCK_TASK: Task = { + id: MOCK_TASK_ID, + meeting_id: MOCK_MEETING_ID, + action_item_id: null, + text: 'Follow up with client', + status: 'open', + assignee_person_id: null, + due_date: null, + priority: MOCK_TASK_PRIORITY, + completed_at: null, +}; + +const MOCK_TASK_WITH_MEETING: TaskWithMeeting = { + task: MOCK_TASK, + meeting_title: 'Sprint Planning', + meeting_created_at: MOCK_TASK_CREATED_AT, + project_id: MOCK_PROJECT_ID, +}; + +const MOCK_PROJECT: Project = { + id: MOCK_PROJECT_ID, + workspace_id: MOCK_WORKSPACE_ID, + name: 'Core', + is_default: true, + is_archived: false, + created_at: MOCK_TASK_CREATED_AT, + updated_at: MOCK_TASK_CREATED_AT, +}; + +const queryState = { + tasksLoading: false, + tasksResponse: undefined as ListTasksResponse | undefined, +}; + +const { mockUseQuery, mockUseMutation, mockInvalidateQueries } = vi.hoisted(() => ({ + mockUseQuery: vi.fn(() => ({ + data: queryState.tasksResponse, + isLoading: queryState.tasksLoading, + })), + mockUseMutation: vi.fn(() => ({ mutate: vi.fn(), isPending: false })), + mockInvalidateQueries: vi.fn(), +})); + +vi.mock('@tanstack/react-query', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useQuery: mockUseQuery, + useMutation: mockUseMutation, + useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }), + }; +}); + +vi.mock('framer-motion', async () => { + const React = await import('react'); + return { + AnimatePresence: ({ children }: { children: React.ReactNode }) => + React.createElement(React.Fragment, null, children), + motion: { + div: (props: React.HTMLAttributes) => React.createElement('div', props), + p: (props: React.HTMLAttributes) => React.createElement('p', props), + }, + }; +}); + +vi.mock('@/components/features/projects/ProjectScopeFilter', () => ({ + ProjectScopeFilter: () =>
ProjectScopeFilter
, +})); + +vi.mock('@/components/features/tasks/TasksKanbanView', () => ({ + TasksKanbanView: () =>
TasksKanbanView
, +})); + +vi.mock('@/lib/preferences', () => ({ + preferences: { + get: () => ({ tasks_view_mode: 'list', tasks_project_scope: 'active', tasks_project_ids: [] }), + setTasksProjectFilter: vi.fn(), + setTasksViewMode: vi.fn(), + }, +})); + +vi.mock('@/lib/utils/format', () => ({ + formatRelativeTime: () => 'moments ago', + formatDate: () => 'Jan 1, 2025', +})); + +vi.mock('@/contexts/project-state', () => ({ + useProjects: () => ({ + activeProject: MOCK_PROJECT, + projects: [MOCK_PROJECT], + isLoading: false, + }), +})); + +describe('TasksPage', () => { + beforeEach(() => { + queryState.tasksLoading = false; + queryState.tasksResponse = { tasks: [], total_count: 0 }; + }); + + it('shows loading skeletons when fetching tasks', () => { + queryState.tasksLoading = true; + queryState.tasksResponse = undefined; + + render( + + + + ); + + expect(document.querySelector('.skeleton-shimmer')).toBeInTheDocument(); + }); + + it('shows empty state when there are no tasks', () => { + render( + + + + ); + + expect(screen.getByText('No open tasks')).toBeInTheDocument(); + expect(screen.getByText('All caught up! Great job.')).toBeInTheDocument(); + }); + + it('renders task list when tasks are available', () => { + queryState.tasksResponse = { tasks: [MOCK_TASK_WITH_MEETING], total_count: MOCK_TASK_TOTAL }; + + render( + + + + ); + + expect(screen.getByText('Follow up with client')).toBeInTheDocument(); + expect(screen.getByText('Sprint Planning')).toBeInTheDocument(); + expect(screen.getByText('moments ago')).toBeInTheDocument(); + }); +}); diff --git a/client/src/pages/Tasks.tsx b/client/src/pages/Tasks.tsx index 4eb2c56..ae27766 100644 --- a/client/src/pages/Tasks.tsx +++ b/client/src/pages/Tasks.tsx @@ -2,15 +2,26 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AnimatePresence, motion } from 'framer-motion'; -import { ArrowRight, Calendar, CheckCircle2, CheckSquare, Circle, Inbox, XCircle } from 'lucide-react'; +import { + ArrowRight, + Calendar, + CheckCircle2, + CheckSquare, + Circle, + Inbox, + LayoutGrid, + List, + XCircle, +} from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { getAPI } from '@/api/interface'; import type { Task, TaskStatus, TaskWithMeeting as APITaskWithMeeting, Priority } from '@/api/types'; -import type { ProjectScope } from '@/api/types/requests'; +import type { ProjectScope, UserPreferences } from '@/api/types/requests/preferences'; import { EmptyState } from '@/components/common'; import { PriorityBadge } from '@/components/common'; import { ProjectScopeFilter } from '@/components/features/projects/ProjectScopeFilter'; +import { TasksKanbanView } from '@/components/features/tasks/TasksKanbanView'; import { CenteredStatsCard } from '@/components/common'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; @@ -52,6 +63,9 @@ export default function TasksPage() { const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('open'); const [priorityFilter, setPriorityFilter] = useState('all'); + const [viewMode, setViewMode] = useState(() => { + return preferences.get().tasks_view_mode ?? 'list'; + }); const [projectScope, setProjectScope] = useState(() => { return preferences.get().tasks_project_scope ?? 'active'; }); @@ -82,10 +96,11 @@ export default function TasksPage() { }); const updateTaskMutation = useMutation({ - mutationFn: (params: { taskId: string; status: TaskStatus }) => + mutationFn: (params: { taskId: string; status?: TaskStatus; text?: string }) => getAPI().updateTask({ task_id: params.taskId, status: params.status, + text: params.text, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks'] }); @@ -94,7 +109,7 @@ export default function TasksPage() { addClientLog({ level: 'warning', source: 'app', - message: 'Failed to update task status', + message: 'Failed to update task', details: error instanceof Error ? error.message : String(error), metadata: { context: 'tasks_page_update' }, }); @@ -105,6 +120,10 @@ export default function TasksPage() { preferences.setTasksProjectFilter(projectScope, selectedProjectIds); }, [projectScope, selectedProjectIds]); + useEffect(() => { + preferences.setTasksViewMode(viewMode); + }, [viewMode]); + const allTasks = useMemo(() => tasksResponse?.tasks ?? [], [tasksResponse]); const filteredTasks = useMemo(() => { @@ -146,6 +165,14 @@ export default function TasksPage() { updateTaskMutation.mutate({ taskId: task.id, status: newStatus }); }; + const handleTextChange = (taskId: string, newText: string) => { + updateTaskMutation.mutate({ taskId, text: newText }); + }; + + const handleStatusChange = (taskId: string, newStatus: TaskStatus) => { + updateTaskMutation.mutate({ taskId, status: newStatus }); + }; + const handleDismiss = (task: Task) => { updateTaskMutation.mutate({ taskId: task.id, status: 'dismissed' }); }; @@ -244,26 +271,49 @@ export default function TasksPage() { />
- {/* Status Tabs */} - setStatusFilter(v as StatusFilter)}> - - - - Open - - - - Done - - - - Dismissed - - All - - + {/* Status Tabs */} + {viewMode === 'list' && ( + setStatusFilter(v as StatusFilter)}> + + + + Open + + + + Done + + + + Dismissed + + All + + + )} + + {/* View Toggle */} +
+ + +
+ + {/* Priority Filter */} - {/* Priority Filter */}
{(['all', 'high', 'medium', 'low'] as const).map((priority) => ( + ), +})); + +vi.mock('@/components/ui/slider', () => ({ + Slider: ({ + onValueChange, + onValueCommit, + }: { + onValueChange?: (value: number[]) => void; + onValueCommit?: (value: number[]) => void; + }) => ( + + ), +})); + +vi.mock('@/lib/utils/format', () => ({ + formatDate: () => 'DATE', + formatDuration: () => 'DUR', +})); + +describe('Meeting detail Header', () => { + const meeting: Meeting = { + id: 'm1', + title: 'Meeting', + created_at: 0, + duration_seconds: 10, + state: 'completed', + }; + + it('renders basic details when not in tauri', () => { + isTauri = false; + render( +
+ ); + + expect(screen.getByText('Meeting')).toBeInTheDocument(); + expect(screen.getByText('DATE')).toBeInTheDocument(); + expect(screen.getByText('DUR')).toBeInTheDocument(); + expect(screen.getByText('completed')).toBeInTheDocument(); + }); + + it('renders playback controls and triggers actions in tauri', () => { + isTauri = true; + const onPause = vi.fn(); + const onPlay = vi.fn(); + const onStop = vi.fn(); + const onSeek = vi.fn(); + const setSeekPosition = vi.fn(); + const onExport = vi.fn(); + + render( +
+ ); + + fireEvent.click(screen.getByText('Pause')); + expect(onPause).toHaveBeenCalled(); + fireEvent.click(screen.getByText('Stop')); + expect(onStop).toHaveBeenCalled(); + + fireEvent.click(screen.getByText('Slider')); + expect(setSeekPosition).toHaveBeenCalledWith(10); + expect(onSeek).toHaveBeenCalledWith(10); + + fireEvent.click(screen.getByText('Markdown (.md)')); + expect(onExport).toHaveBeenCalledWith('markdown'); + }); + + it('disables entity extraction while running', () => { + isTauri = false; + render( +
+ ); + + const button = screen.getByRole('button', { name: /entities/i }); + expect(button).toBeDisabled(); + }); +}); diff --git a/client/src/pages/meeting-detail/summary-panel.test.tsx b/client/src/pages/meeting-detail/summary-panel.test.tsx new file mode 100644 index 0000000..407d315 --- /dev/null +++ b/client/src/pages/meeting-detail/summary-panel.test.tsx @@ -0,0 +1,66 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Summary } from '@/api/types'; +import { SummaryPanel } from './summary-panel'; + +const buildSummary = (overrides: Partial = {}): Summary => ({ + meeting_id: 'meeting-1', + executive_summary: 'Summary text', + key_points: [ + { text: 'Key point', segment_ids: [1], start_time: 0, end_time: 1 }, + ], + action_items: [ + { text: 'Action', priority: 'high', segment_ids: [1], assignee: 'Alex' }, + ], + generated_at: 0, + model_version: 'v1', + ...overrides, +}); + +describe('SummaryPanel', () => { + it('renders compact empty summary message', () => { + render(); + + expect( + screen.getByText('No summary available. Click "Generate Summary" to create one.') + ).toBeInTheDocument(); + }); + + it('renders empty state with generate button', () => { + const onGenerateSummary = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button', { name: 'Generate Summary' })); + expect(onGenerateSummary).toHaveBeenCalled(); + }); + + it('renders summary content, metadata, and action items', () => { + render( + + ); + + expect(screen.getByText('Executive Summary')).toBeInTheDocument(); + expect(screen.getByText('Summary text')).toBeInTheDocument(); + expect(screen.getByText('Duration:')).toBeInTheDocument(); + expect(screen.getByText('10m')).toBeInTheDocument(); + expect(screen.getByText('Key point')).toBeInTheDocument(); + expect(screen.getByText('Action')).toBeInTheDocument(); + expect(screen.getByText('Alex')).toBeInTheDocument(); + }); + + it('renders empty action items message', () => { + render( + + ); + + expect(screen.getByText('No action items detected.')).toBeInTheDocument(); + }); +}); diff --git a/client/src/pages/meeting-detail/use-meeting-detail.ts b/client/src/pages/meeting-detail/use-meeting-detail.ts index 0d017c3..41b1393 100644 --- a/client/src/pages/meeting-detail/use-meeting-detail.ts +++ b/client/src/pages/meeting-detail/use-meeting-detail.ts @@ -176,7 +176,7 @@ export function useMeetingDetail({ meetingId }: UseMeetingDetailProps) { metadata: { meeting_id: meetingId }, }); fireAndForget(startProcessing(meetingId), 'auto-start-processing'); - }, [meeting, meetingId, shouldAutoStart, startProcessing]); + }, [failedStep, meeting, meetingId, shouldAutoStart, startProcessing]); const handleGenerateSummary = async () => { if (!meeting) { diff --git a/client/vitest_output.txt b/client/vitest_output.txt new file mode 100644 index 0000000..040953b --- /dev/null +++ b/client/vitest_output.txt @@ -0,0 +1,8685 @@ + +> noteflow-client@0.1.0 test +> vitest run + + + RUN  v4.0.17 /home/trav/repos/noteflow/client + + ✓ src/api/adapters/tauri/stream.test.ts (19 tests) 447ms + ✓ src/components/ui/dropdown-menu.test.tsx (1 test) 262ms +stderr | src/hooks/audio/use-asr-config.test.ts > useAsrConfig > updateConfig > updates config when job completes with new configuration +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) + +stderr | src/hooks/audio/use-asr-config.test.ts > useAsrConfig > updateConfig > refreshes config when job completes without new configuration +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Loading State > shows loading state while fetching logs +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Loading State > shows loading state while fetching logs +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/hooks/audio/use-asr-config.test.ts > useAsrConfig > updateConfig > stops reconfiguring and reports failure status +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Empty State > shows empty state when no logs +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/hooks/audio/use-asr-config.test.ts > useAsrConfig > updateConfig > stops reconfiguring and reports cancelled status +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Empty State > suggests adjusting filters when filtered with no results +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Display > renders log entries from API response +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/hooks/auth/use-cloud-consent.test.ts (14 tests) 746ms + ✓ src/hooks/audio/use-asr-config.test.ts (12 tests) 638ms + ✓ src/hooks/sync/use-integration-sync.test.ts (37 tests) 610ms + ✓ polls until sync completes when initial status is running  502ms +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Display > displays log stats for each level +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Display > shows source badges for log entries +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/components/features/notes/timestamped-notes-editor.test.tsx (4 tests) 480ms +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Display > renders client logs alongside server logs +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/hooks/auth/use-huggingface-token.test.ts (17 tests) 910ms +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Filtering > calls API with level filter when selected +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/components/features/recording/audio-device-selector.test.tsx (3 tests) 404ms + ✓ src/pages/Recording.logic.test.tsx (7 tests | 2 skipped) 1000ms + ✓ shows desktop-only message when not running in tauri without simulation  663ms +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Filtering > filters logs by search query client-side +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Filtering > filters logs by metadata values +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Refresh > refetches logs when refresh button clicked +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/components/features/entities/entity-management-panel.test.tsx (4 tests) 621ms + ✓ adds, edits, and deletes entities when persisted  414ms +stderr | src/pages/Recording.test.tsx > RecordingPage > shows desktop-only message when not running in Tauri +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Details > renders log with metadata that can be expanded +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Details > shows trace and span badges when correlation IDs are present +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Log Details > handles logs without details +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Export > exports logs and revokes the object URL +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + +stderr | src/pages/Recording.test.tsx > RecordingPage > allows simulated recording when enabled in preferences +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) +Warning: An update to ProjectProvider inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) + +stderr | src/components/features/analytics/logs-tab.test.tsx > LogsTab > Footer > shows log count in footer +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) +Warning: An update to Tooltip inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at Tooltip (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:73:5) + at div + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-primitive/dist/index.mjs:28:13 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:32:5 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:33:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs:9:13 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:27:15 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at CollectionProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-collection/dist/index.mjs:17:13) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-roving-focus/dist/index.mjs:26:68 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:97:7 + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:34:12 + at file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-toggle-group/dist/index.mjs:19:11 + at /home/trav/repos/noteflow/client/src/components/ui/toggle-group.tsx:17:71 + at div + at Provider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-context/dist/index.mjs:27:15) + at TooltipProvider (file:///home/trav/repos/noteflow/client/node_modules/@radix-ui/react-tooltip/dist/index.mjs:29:5) + at div + at div + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:53:71 + at div + at /home/trav/repos/noteflow/client/src/components/ui/card.tsx:13:64 + at div + at LogsTab (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.tsx:66:77) + at QueryClientProvider (file:///home/trav/repos/noteflow/client/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js:20:3) + at Wrapper (/home/trav/repos/noteflow/client/src/components/features/analytics/logs-tab.test.tsx:167:31) + + ✓ src/components/features/analytics/logs-tab.test.tsx (16 tests) 1054ms +TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at HTMLUnknownElement.callCallback (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:4164:14) + at HTMLUnknownElement.callTheUserObjectsOperation (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) + at innerInvokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:360:16) + at invokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:296:3) + at HTMLUnknownElementImpl._dispatch (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:243:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:114:17) + ✓ src/hooks/processing/use-post-processing.test.ts (30 tests) 966ms +TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at HTMLUnknownElement.callCallback (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:4164:14) + at HTMLUnknownElement.callTheUserObjectsOperation (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) + at innerInvokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:360:16) + at invokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:296:3) + at HTMLUnknownElementImpl._dispatch (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:243:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:114:17) +stderr | src/pages/Recording.test.tsx > RecordingPage - GAP-006 Connection Bootstrapping > attempts preflight connect when starting recording while disconnected +Error handled by React Router default ErrorBoundary: TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26124:20) + at flushSyncCallbacks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:12042:22) +Error handled by React Router default ErrorBoundary: TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at recoverFromConcurrentError (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:25889:20) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26135:20) +The above error occurred in the component: + + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:68:33) + at div + at UnifiedStatusRow (/home/trav/repos/noteflow/client/src/components/features/recording/unified-status-row.tsx:10:29) + at div + at div + at div + at div + at RecordingHeader (/home/trav/repos/noteflow/client/src/components/features/recording/recording-header.tsx:20:28) + at div + at RecordingPage (/home/trav/repos/noteflow/client/src/pages/Recording.tsx:50:59) + at RenderedRoute (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:539:7) + at RenderErrorBoundary (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:486:7) + at DataRoutes (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:684:7) + at Router (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:1207:17) + at RouterProvider (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:455:7) + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) + +React will try to recreate this component tree from scratch using the error boundary you provided, RenderErrorBoundary. +React Router caught the following error during render TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at recoverFromConcurrentError (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:25889:20) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26135:20) { + componentStack: '\n' + + ' at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:68:33)\n' + + ' at div\n' + + ' at UnifiedStatusRow (/home/trav/repos/noteflow/client/src/components/features/recording/unified-status-row.tsx:10:29)\n' + + ' at div\n' + + ' at div\n' + + ' at div\n' + + ' at div\n' + + ' at RecordingHeader (/home/trav/repos/noteflow/client/src/components/features/recording/recording-header.tsx:20:28)\n' + + ' at div\n' + + ' at RecordingPage (/home/trav/repos/noteflow/client/src/pages/Recording.tsx:50:59)\n' + + ' at RenderedRoute (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:539:7)\n' + + ' at RenderErrorBoundary (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:486:7)\n' + + ' at DataRoutes (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:684:7)\n' + + ' at Router (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:1207:17)\n' + + ' at RouterProvider (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:455:7)\n' + + ' at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28)\n' + + ' at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30)\n' + + ' at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31)\n' + + ' at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20)' +} + + ✓ src/api/adapters/tauri/__tests__/transcription-mapping.test.ts (8 tests) 189ms + ✓ src/components/features/meetings/processing-status.test.tsx (22 tests) 440ms +TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at HTMLUnknownElement.callCallback (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:4164:14) + at HTMLUnknownElement.callTheUserObjectsOperation (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) + at innerInvokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:360:16) + at invokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:296:3) + at HTMLUnknownElementImpl._dispatch (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:243:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:114:17) +TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at HTMLUnknownElement.callCallback (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:4164:14) + at HTMLUnknownElement.callTheUserObjectsOperation (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) + at innerInvokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:360:16) + at invokeEventListeners (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:296:3) + at HTMLUnknownElementImpl._dispatch (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:243:9) + at HTMLUnknownElementImpl.dispatchEvent (/home/trav/repos/noteflow/client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:114:17) +stderr | src/pages/Recording.test.tsx > RecordingPage - GAP-006 Connection Bootstrapping > skips preflight connect when already connected +Error handled by React Router default ErrorBoundary: TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26124:20) + at flushSyncCallbacks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:12042:22) +Error handled by React Router default ErrorBoundary: TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at recoverFromConcurrentError (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:25889:20) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26135:20) +The above error occurred in the component: + + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:68:33) + at div + at UnifiedStatusRow (/home/trav/repos/noteflow/client/src/components/features/recording/unified-status-row.tsx:10:29) + at div + at div + at div + at div + at RecordingHeader (/home/trav/repos/noteflow/client/src/components/features/recording/recording-header.tsx:20:28) + at div + at RecordingPage (/home/trav/repos/noteflow/client/src/pages/Recording.tsx:50:59) + at RenderedRoute (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:539:7) + at RenderErrorBoundary (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:486:7) + at DataRoutes (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:684:7) + at Router (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:1207:17) + at RouterProvider (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:455:7) + at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28) + at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30) + at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31) + at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20) + +React will try to recreate this component tree from scratch using the error boundary you provided, RenderErrorBoundary. +React Router caught the following error during render TypeError: Cannot read properties of undefined (reading 'icon') + at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:108:23) + at renderWithHooks (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:15486:18) + at mountIndeterminateComponent (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:20103:13) + at beginWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:21626:16) + at beginWork$1 (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:27465:14) + at performUnitOfWork (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26599:12) + at workLoopSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26505:5) + at renderRootSync (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26473:7) + at recoverFromConcurrentError (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:25889:20) + at performSyncWorkOnRoot (/home/trav/repos/noteflow/client/node_modules/react-dom/cjs/react-dom.development.js:26135:20) { + componentStack: '\n' + + ' at ApiModeIndicator (/home/trav/repos/noteflow/client/src/components/features/connectivity/api-mode-indicator.tsx:68:33)\n' + + ' at div\n' + + ' at UnifiedStatusRow (/home/trav/repos/noteflow/client/src/components/features/recording/unified-status-row.tsx:10:29)\n' + + ' at div\n' + + ' at div\n' + + ' at div\n' + + ' at div\n' + + ' at RecordingHeader (/home/trav/repos/noteflow/client/src/components/features/recording/recording-header.tsx:20:28)\n' + + ' at div\n' + + ' at RecordingPage (/home/trav/repos/noteflow/client/src/pages/Recording.tsx:50:59)\n' + + ' at RenderedRoute (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:539:7)\n' + + ' at RenderErrorBoundary (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:486:7)\n' + + ' at DataRoutes (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:684:7)\n' + + ' at Router (/home/trav/repos/noteflow/client/node_modules/react-router/dist/umd/react-router.development.js:1207:17)\n' + + ' at RouterProvider (/home/trav/repos/noteflow/client/node_modules/react-router-dom/dist/umd/react-router-dom.development.js:455:7)\n' + + ' at ProjectProvider (/home/trav/repos/noteflow/client/src/contexts/project-context.tsx:59:28)\n' + + ' at WorkspaceProvider (/home/trav/repos/noteflow/client/src/contexts/workspace-context.tsx:49:30)\n' + + ' at ConnectionProvider (/home/trav/repos/noteflow/client/src/contexts/connection-context.tsx:18:31)\n' + + ' at Wrapper (/home/trav/repos/noteflow/client/src/pages/Recording.test.tsx:93:20)' +} + + ✓ src/pages/Recording.test.tsx (5 tests) 767ms + ✓ src/components/features/sync/preferences-sync-status.test.tsx (2 tests) 222ms + ✓ src/components/features/analytics/performance-tab.test.tsx (16 tests) 783ms + ✓ src/components/features/settings/export-ai-section.test.tsx (16 tests) 390ms + ✓ src/hooks/auth/use-oauth-flow.test.ts (22 tests) 99ms + ✓ src/components/features/entities/entity-highlight.test.tsx (3 tests) 204ms + ✓ src/test/code-quality.test.ts (28 tests) 415ms + ✓ src/contexts/workspace-context.test.tsx (2 tests) 252ms + ✓ src/components/features/recording/recording-header.test.tsx (3 tests) 238ms + ✓ src/hooks/data/use-guarded-mutation.test.tsx (2 tests) 146ms + ✓ src/components/features/workspace/workspace-switcher.test.tsx (2 tests) 377ms + ✓ lists workspaces and switches on selection  367ms + ✓ src/components/features/recording/buffering-indicator.test.tsx (4 tests) 63ms + ✓ src/components/ui/resizable.test.tsx (1 test) 151ms + ✓ src/api/adapters/mock/index.test.ts (6 tests) 174ms +stderr | src/hooks/audio/use-audio-devices.test.ts > useAudioDevices > ignores tauri audio test events when not testing input +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) +Warning: An update to TestComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ + +This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act + at TestComponent (/home/trav/repos/noteflow/client/node_modules/@testing-library/react/dist/pure.js:328:5) + + ✓ src/hooks/audio/use-audio-devices.test.ts (23 tests) 178ms + ✓ src/hooks/processing/use-diarization.test.ts (26 tests) 128ms + ✓ src/lib/audio/device-ids.test.ts (21 tests) 18ms + ✓ src/components/features/recording/audio-level-meter.test.tsx (10 tests) 119ms + ✓ src/hooks/auth/use-oidc-providers.test.ts (23 tests) 77ms + ✓ src/contexts/connection-context.test.tsx (4 tests) 47ms + ✓ src/components/features/recording/recording-components.test.tsx (4 tests) 80ms + ✓ src/components/features/settings/integrations-section/use-integration-handlers.test.tsx (6 tests) 31ms + ✓ src/integration/recording-session.integration.test.tsx (5 tests) 78ms + ✓ src/components/features/recording/stat-card.test.tsx (5 tests) 47ms + ✓ src/lib/storage/crypto.test.ts (8 tests) 7ms + ✓ src/components/features/connectivity/offline-banner.test.tsx (5 tests) 49ms + ✓ src/lib/system/events.test.tsx (6 tests) 17ms + ✓ src/components/features/recording/transcript-segment-actions.test.tsx (3 tests) 53ms + ✓ src/components/features/recording/speaker-distribution.test.tsx (2 tests) 32ms + ✓ src/api/index.test.ts (5 tests) 18ms + ✓ src/components/features/recording/unified-status-row.test.tsx (4 tests) 55ms + ✓ src/components/features/recording/notes-quick-actions.test.tsx (2 tests) 44ms + ✓ src/components/common/badges/speaker-badge.test.tsx (5 tests) 61ms + ✓ src/api/adapters/cached/index.test.ts (8 tests) 64ms + ✓ src/components/features/recording/idle-state.test.tsx (5 tests) 48ms + ✓ src/components/features/recording/confidence-indicator.test.tsx (7 tests) 44ms + ✓ src/lib/observability/summarizer.test.ts (27 tests) 55ms + ✓ src/components/features/connectivity/api-mode-indicator.test.tsx (3 tests) 44ms + ✓ src/lib/ai-providers/strategies/strategies.test.ts (99 tests) 27ms + ✓ src/components/ui/ui-components.test.tsx (5 tests) 89ms + ✓ src/lib/observability/events.test.ts (32 tests) 16ms + ✓ src/lib/preferences/sync.test.ts (13 tests) 12ms + ✓ src/components/features/recording/vad-indicator.test.tsx (6 tests) 28ms + ✓ src/lib/cache/meeting-cache.test.ts (18 tests) 13ms + ✓ src/lib/utils/format.test.ts (40 tests) 24ms + ✓ src/api/adapters/tauri/__tests__/environment.test.ts (3 tests) 17ms + ✓ src/api/core/reconnection.test.ts (10 tests) 27ms + ✓ src/hooks/ui/use-toast.test.ts (5 tests) 24ms + ✓ src/api/adapters/mock/stream.test.ts (5 tests) 16ms + ✓ src/lib/observability/groups.test.ts (18 tests) 11ms + ✓ src/lib/state/entities.test.ts (47 tests) 18ms + ✓ src/lib/observability/messages.test.ts (42 tests) 9ms + ✓ src/hooks/ui/use-panel-preferences.test.ts (4 tests) 34ms + ✓ src/api/core/helpers.test.ts (13 tests) 10ms + ✓ src/api/types/errors.test.ts (37 tests) 12ms + ✓ src/lib/observability/group-summarizer.test.ts (27 tests) 13ms + ✓ src/api/adapters/tauri/__tests__/misc-mapping.test.ts (8 tests) 26ms + ✓ src/lib/observability/events.integration.test.ts (24 tests) 9ms + ✓ src/api/adapters/tauri/__tests__/core-mapping.test.ts (8 tests) 18ms + ✓ src/lib/integrations/utils.test.ts (6 tests) 3ms + ✓ src/lib/utils/index.test.ts (7 tests) 8ms + ✓ src/lib/config/config.test.ts (4 tests) 4ms + ✓ src/lib/observability/client.test.ts (2 tests) 7ms + ✓ src/lib/observability/converters.test.ts (18 tests) 5ms + ✓ src/api/adapters/mock/data.test.ts (6 tests) 7ms + ✓ src/lib/preferences/validation.test.ts (3 tests) 6ms + ✓ src/lib/utils/object.test.ts (5 tests) 3ms + ✓ src/lib/audio/device-persistence.integration.test.ts (8 tests) 5ms + ✓ src/api/adapters/tauri/constants.test.ts (4 tests) 4ms + ✓ src/lib/audio/speaker.test.ts (2 tests) 4ms + ✓ src/api/core/connection.test.ts (3 tests) 2ms + ✓ src/lib/ui/cva.test.ts (1 test) 2ms + ✓ src/components/features/recording/index.test.ts (1 test) 5ms + + Test Files  87 passed (87) + Tests  1055 passed | 2 skipped (1057) + Start at  10:36:55 + Duration  9.55s (transform 11.48s, setup 33.30s, import 18.47s, tests 14.96s, environment 37.69s) + diff --git a/client/wdio.mac.conf.ts b/client/wdio.mac.conf.ts index fbfa4fa..2ff9257 100644 --- a/client/wdio.mac.conf.ts +++ b/client/wdio.mac.conf.ts @@ -63,15 +63,11 @@ async function ensureAppiumServer(): Promise { const details = [message, cause].filter(Boolean).join(' '); if (details.includes('EPERM') || details.includes('Operation not permitted')) { throw new Error( - 'Local network access appears blocked for this process. ' + - 'Allow your terminal (or Codex) under System Settings → Privacy & Security → Local Network, ' + - 'and ensure no firewall blocks 127.0.0.1:4723.' + 'Local network access appears blocked for this process. Allow your terminal (or Codex) under System Settings → Privacy & Security → Local Network, and ensure no firewall blocks 127.0.0.1:4723.' ); } throw new Error( - `Appium server not reachable at ${statusUrl}. ` + - 'Start it with: appium --base-path / --log-level error\n' + - `Details: ${details}` + `Appium server not reachable at ${statusUrl}. Start it with: appium --base-path / --log-level error\nDetails: ${details}` ); } } @@ -81,10 +77,7 @@ function ensureXcodeAvailable(): void { if (result.error || result.status !== 0) { const message = result.stderr?.trim() || result.stdout?.trim() || 'xcodebuild not available'; throw new Error( - 'Xcode is required for the mac2 driver (WebDriverAgentMac). ' + - 'Install Xcode and select it with:\n' + - ' sudo xcode-select -s /Applications/Xcode.app/Contents/Developer\n' + - `Details: ${message}` + `Xcode is required for the mac2 driver (WebDriverAgentMac). Install Xcode and select it with:\n sudo xcode-select -s /Applications/Xcode.app/Contents/Developer\nDetails: ${message}` ); } } @@ -97,19 +90,17 @@ function ensureDeveloperModeEnabled(): void { } if (output.includes('disabled')) { throw new Error( - 'Developer mode is disabled. Enable it in System Settings → Privacy & Security → Developer Mode.\n' + - 'You can also enable dev tools access via CLI:\n' + - ' sudo /usr/sbin/DevToolsSecurity -enable\n' + - ' sudo dseditgroup -o edit -a "$(whoami)" -t user _developer\n' + - 'Then log out and back in.' + `Developer mode is disabled. Enable it in System Settings → Privacy & Security → Developer Mode. +You can also enable dev tools access via CLI: + sudo /usr/sbin/DevToolsSecurity -enable + sudo dseditgroup -o edit -a "$(whoami)" -t user _developer +Then log out and back in.` ); } if (result.error || result.status !== 0) { const message = result.stderr?.trim() || result.stdout?.trim() || 'DevToolsSecurity failed'; writeStderr( - 'Warning: Unable to read developer mode status. ' + - 'Verify it is enabled in System Settings → Privacy & Security → Developer Mode. ' + - `Details: ${message}` + `Warning: Unable to read developer mode status. Verify it is enabled in System Settings → Privacy & Security → Developer Mode. Details: ${message}` ); } } @@ -130,9 +121,7 @@ function ensureAutomationModeConfigured(): void { // If it does, the user needs to run the enable command if (requiresAuth && !doesNotRequireAuth) { throw new Error( - 'Automation Mode requires user authentication. Configure it with:\n' + - ' sudo automationmodetool enable-automationmode-without-authentication\n' + - 'This allows WebDriverAgentMac to enable automation mode without prompts.' + 'Automation Mode requires user authentication. Configure it with:\n sudo automationmodetool enable-automationmode-without-authentication\nThis allows WebDriverAgentMac to enable automation mode without prompts.' ); } @@ -190,7 +179,7 @@ export const config: Options.Testrunner = { onPrepare: async () => { if (!fs.existsSync(APP_BUNDLE_PATH)) { throw new Error( - `Tauri app bundle not found at: ${APP_BUNDLE_PATH}\n` + 'Build it with: npm run tauri:build' + `Tauri app bundle not found at: ${APP_BUNDLE_PATH}\nBuild it with: npm run tauri:build` ); } fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); diff --git a/coverage_output.txt b/coverage_output.txt new file mode 100644 index 0000000..a55dae5 --- /dev/null +++ b/coverage_output.txt @@ -0,0 +1,29 @@ +============================= test session starts ============================== +platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0 +benchmark: 5.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) +rootdir: /home/trav/repos/noteflow +configfile: pyproject.toml +testpaths: tests +plugins: anyio-4.12.0, httpx-0.36.0, benchmark-5.2.3, asyncio-1.3.0, cov-7.0.0 +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function +collected 3202 items + +tests/application/test_analytics_service.py .......... [ 0%] +tests/application/test_asr_config_service.py .................. [ 0%] +tests/application/test_auth_service.py ................................. [ 1%] +. [ 1%] +tests/application/test_calendar_service.py ............... [ 2%] +tests/application/test_export_service.py ..... [ 2%] +tests/application/test_hf_token_service.py ........................ [ 3%] +tests/application/test_meeting_service.py ............................. [ 4%] +tests/application/test_ner_service.py ............. [ 4%] +tests/application/test_project_service.py .............................. [ 5%] +.... [ 5%] +tests/application/test_recovery_service.py ................. [ 6%] +tests/application/test_retention_service.py ........... [ 6%] +tests/application/test_summarization_service.py ........................ [ 7%] +.... [ 7%] +tests/application/test_task_service.py ................... [ 8%] +tests/application/test_trigger_service.py ................. [ 8%] +tests/application/test_webhook_service.py .............. [ 8%] +tests/benchmarks/test_hot_paths.py ........................ \ No newline at end of file diff --git a/docs/sprints/phase-ongoing/Bugfinder/strategy-b-implementation.md b/docs/sprints/phase-ongoing/Bugfinder/strategy-b-implementation.md index 84aad6d..c86a12b 100644 --- a/docs/sprints/phase-ongoing/Bugfinder/strategy-b-implementation.md +++ b/docs/sprints/phase-ongoing/Bugfinder/strategy-b-implementation.md @@ -352,9 +352,24 @@ export function SummaryPanel({ summary, summaryMeta, onGenerateSummary }: Summar - [x] `client/src-tauri/src/grpc/client/tasks.rs` — Rust gRPC client methods - [x] `client/src-tauri/src/grpc/types/analytics.rs` — Rust type definitions - [x] `client/src-tauri/src/grpc/types/tasks.rs` — Rust type definitions -- [x] `client/src/pages/Analytics.tsx` — use aggregates (uses getAnalyticsOverview, listSpeakerStats) +- [x] `client/src/pages/Analytics.tsx` — use aggregates (uses getAnalyticsOverview, listSpeakerStats, getEntityAnalytics) - [x] `client/src/pages/People.tsx` — use speaker stats (uses listSpeakerStats endpoint) - [x] `client/src/pages/Tasks.tsx` — use TaskModel data (uses listTasks, updateTask) + +**NER Entity Analytics (Added 2026-01-22)**: +- [x] `src/noteflow/domain/entities/analytics.py` — EntityAnalytics, EntityCategoryStat, TopEntity domain models +- [x] `src/noteflow/infrastructure/persistence/repositories/analytics_repo.py` — get_entity_analytics() SQL aggregates +- [x] `src/noteflow/application/services/analytics/service.py` — get_entity_analytics() with caching +- [x] `src/noteflow/grpc/proto/noteflow.proto` — GetEntityAnalytics RPC + messages +- [x] `src/noteflow/grpc/mixins/analytics_mixin.py` — GetEntityAnalytics handler +- [x] `src/noteflow/grpc/mixins/converters/_task_analytics.py` — entity_category_stat_to_proto, top_entity_to_proto +- [x] `client/src/api/types/features/analytics.ts` — EntityCategoryStat, TopEntity, EntityAnalytics types +- [x] `client/src/api/interface.ts` — getEntityAnalytics method +- [x] `client/src/api/adapters/tauri/sections/analytics.ts` — getEntityAnalytics implementation +- [x] `client/src-tauri/src/grpc/types/analytics.rs` — EntityCategoryStat, TopEntity, EntityAnalytics structs +- [x] `client/src-tauri/src/grpc/client/analytics.rs` — get_entity_analytics() gRPC client +- [x] `client/src-tauri/src/commands/analytics.rs` — get_entity_analytics Tauri command +- [x] `client/src/pages/Analytics.tsx` — Entities tab with category charts and top entities table - [x] `client/src/pages/meeting-detail/index.tsx` — summary in collapsible section above transcript - [x] `client/src/pages/meeting-detail/summary-panel.tsx` — added compact mode with horizontal grid layout @@ -387,13 +402,50 @@ export function SummaryPanel({ summary, summaryMeta, onGenerateSummary }: Summar - [x] `pytest tests/application/test_analytics_service.py` passes (10 tests) - [x] `pytest tests/grpc/test_tasks_mixin.py` passes (7 tests) - [x] `pytest tests/grpc/test_analytics_mixin.py` passes (6 tests) -- [ ] `make quality` passes +- [x] `make quality` passes (verified 2026-01-22) - [x] No type suppression comments or loose typing introduced +### Integration Tests Added (2026-01-22) + +- [x] `tests/integration/test_task_repository.py` — Task CRUD round-trip (create, get, update, delete, list with filters) +- [x] `tests/integration/test_analytics_repository.py` — Analytics aggregation queries +- [x] `client/e2e/tasks.spec.ts` — Tasks API and UI E2E tests + --- ## Post-Sprint -- [ ] Add kanban view for tasks and inline editing -- [ ] Add NER-based entity analytics (NamedEntityModel aggregates) -- [ ] Materialized analytics tables if cache misses become expensive +- [x] Add kanban view for tasks and inline editing — Already implemented (TasksKanbanView with drag-drop and inline editing) +- [x] Add NER-based entity analytics (NamedEntityModel aggregates) — Implemented 2026-01-22 +- [x] Materialized analytics tables for performance — Implemented 2026-01-22 + +### Materialized Views Implementation + +**Migration**: `w7x8y9z0a1b2_add_analytics_materialized_views.py` + +**Materialized Views Created**: +- `mv_daily_meeting_stats` — Daily meeting count, duration, word count by workspace/project +- `mv_meeting_totals` — Total meetings, duration, segments, speakers, words +- `mv_speaker_stats` — Speaker segment count, total time, meeting count, confidence +- `mv_entity_category_stats` — Entity count and mentions by category +- `mv_top_entities` — Top entities by mention count +- `mv_entity_totals` — Total entities and mentions + +**Repository Methods** (`analytics_repo.py`): +- `get_overview_fast()` — Uses MV with fallback to SQL +- `get_speaker_stats_fast()` — Uses MV with fallback to SQL +- `get_entity_analytics_fast()` — Uses MV with fallback to SQL +- `refresh_all_views()` — Refresh all materialized views +- `refresh_meeting_views()` — Refresh meeting-related views +- `refresh_entity_views()` — Refresh entity-related views + +**Service Methods** (`AnalyticsService`): +- `refresh_materialized_views()` — Refresh all views +- `refresh_meeting_views()` — Refresh meeting views after meeting updates +- `refresh_entity_views()` — Refresh entity views after NER extraction + +**Behavior**: +- Materialized views are used automatically when available +- Falls back to regular SQL queries if views don't exist or for time-filtered queries +- Views should be refreshed periodically or after batch data updates +- `REFRESH MATERIALIZED VIEW CONCURRENTLY` allows non-blocking refresh diff --git a/pytest_output.txt b/pytest_output.txt new file mode 100644 index 0000000..a55dae5 --- /dev/null +++ b/pytest_output.txt @@ -0,0 +1,29 @@ +============================= test session starts ============================== +platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0 +benchmark: 5.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) +rootdir: /home/trav/repos/noteflow +configfile: pyproject.toml +testpaths: tests +plugins: anyio-4.12.0, httpx-0.36.0, benchmark-5.2.3, asyncio-1.3.0, cov-7.0.0 +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function +collected 3202 items + +tests/application/test_analytics_service.py .......... [ 0%] +tests/application/test_asr_config_service.py .................. [ 0%] +tests/application/test_auth_service.py ................................. [ 1%] +. [ 1%] +tests/application/test_calendar_service.py ............... [ 2%] +tests/application/test_export_service.py ..... [ 2%] +tests/application/test_hf_token_service.py ........................ [ 3%] +tests/application/test_meeting_service.py ............................. [ 4%] +tests/application/test_ner_service.py ............. [ 4%] +tests/application/test_project_service.py .............................. [ 5%] +.... [ 5%] +tests/application/test_recovery_service.py ................. [ 6%] +tests/application/test_retention_service.py ........... [ 6%] +tests/application/test_summarization_service.py ........................ [ 7%] +.... [ 7%] +tests/application/test_task_service.py ................... [ 8%] +tests/application/test_trigger_service.py ................. [ 8%] +tests/application/test_webhook_service.py .............. [ 8%] +tests/benchmarks/test_hot_paths.py ........................ \ No newline at end of file diff --git a/run_test.sh b/run_test.sh new file mode 100644 index 0000000..385f01a --- /dev/null +++ b/run_test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd /home/trav/repos/noteflow +source .venv/bin/activate +pytest tests/quality/test_code_smells.py::test_no_long_parameter_lists -v --tb=short 2>&1 diff --git a/src/noteflow/application/services/analytics/refresh.py b/src/noteflow/application/services/analytics/refresh.py new file mode 100644 index 0000000..e69de29 diff --git a/src/noteflow/application/services/analytics/service.py b/src/noteflow/application/services/analytics/service.py index 9a3964f..38bc211 100644 --- a/src/noteflow/application/services/analytics/service.py +++ b/src/noteflow/application/services/analytics/service.py @@ -7,8 +7,10 @@ from datetime import datetime from typing import TYPE_CHECKING, Final from uuid import UUID -from noteflow.domain.entities.analytics import AnalyticsOverview, SpeakerStat +from noteflow.domain.constants.fields import ANALYTICS_CACHE_HIT, ANALYTICS_CACHE_MISS +from noteflow.domain.entities.analytics import AnalyticsOverview, EntityAnalytics, SpeakerStat from noteflow.domain.utils.time import utc_now +from noteflow.domain.value_objects import EntityAnalyticsQueryParams from noteflow.infrastructure.logging import get_logger if TYPE_CHECKING: @@ -23,6 +25,16 @@ logger = get_logger(__name__) DEFAULT_CACHE_TTL_SECONDS: Final[int] = 60 +def _entity_cache_key_from_params(params: EntityAnalyticsQueryParams) -> _EntityCacheKey: + return _EntityCacheKey( + workspace_id=params.workspace_id, + project_ids=tuple(params.project_ids) if params.project_ids else None, + start_time=params.start_time, + end_time=params.end_time, + top_limit=params.top_limit, + ) + + @dataclass(frozen=True, slots=True) class _CacheKey: workspace_id: UUID @@ -31,9 +43,18 @@ class _CacheKey: end_time: datetime | None +@dataclass(frozen=True, slots=True) +class _EntityCacheKey: + workspace_id: UUID + project_ids: tuple[UUID, ...] | None + start_time: datetime | None + end_time: datetime | None + top_limit: int + + @dataclass(slots=True) class _CacheEntry: - data: AnalyticsOverview | list[SpeakerStat] + data: AnalyticsOverview | list[SpeakerStat] | EntityAnalytics expires_at: datetime @@ -47,6 +68,7 @@ class AnalyticsService: self._cache_ttl_seconds = cache_ttl_seconds self._overview_cache: dict[_CacheKey, _CacheEntry] = {} self._speaker_cache: dict[_CacheKey, _CacheEntry] = {} + self._entity_cache: dict[_EntityCacheKey, _CacheEntry] = {} async def get_overview( self, @@ -64,13 +86,11 @@ class AnalyticsService: cached = self._get_cached_overview(cache_key) if cached is not None: - logger.debug( - "analytics_cache_hit", cache_type="overview", workspace_id=str(workspace_id) - ) + logger.debug(ANALYTICS_CACHE_HIT, cache_type="overview", workspace_id=str(workspace_id)) return cached async with self._uow_factory() as uow: - overview = await uow.analytics.get_overview( + overview = await uow.analytics.get_overview_fast( workspace_id=workspace_id, project_ids=project_ids, start_time=start_time, @@ -79,7 +99,7 @@ class AnalyticsService: self._set_cached_overview(cache_key, overview) logger.debug( - "analytics_cache_miss", + ANALYTICS_CACHE_MISS, cache_type="overview", workspace_id=str(workspace_id), total_meetings=overview.total_meetings, @@ -103,12 +123,12 @@ class AnalyticsService: cached = self._get_cached_speaker_stats(cache_key) if cached is not None: logger.debug( - "analytics_cache_hit", cache_type="speaker_stats", workspace_id=str(workspace_id) + ANALYTICS_CACHE_HIT, cache_type="speaker_stats", workspace_id=str(workspace_id) ) return cached async with self._uow_factory() as uow: - stats = await uow.analytics.get_speaker_stats( + stats = await uow.analytics.get_speaker_stats_fast( workspace_id=workspace_id, project_ids=project_ids, start_time=start_time, @@ -117,28 +137,81 @@ class AnalyticsService: self._set_cached_speaker_stats(cache_key, stats) logger.debug( - "analytics_cache_miss", + ANALYTICS_CACHE_MISS, cache_type="speaker_stats", workspace_id=str(workspace_id), speaker_count=len(stats), ) return stats + async def get_entity_analytics( + self, + params: EntityAnalyticsQueryParams, + ) -> EntityAnalytics: + cache_key = _entity_cache_key_from_params(params) + workspace_id_str = str(cache_key.workspace_id) + + cached = self._get_cached_entity_analytics(cache_key) + if cached is not None: + logger.debug( + ANALYTICS_CACHE_HIT, + cache_type="entity_analytics", + workspace_id=workspace_id_str, + ) + return cached + + async with self._uow_factory() as uow: + analytics = await uow.analytics.get_entity_analytics_fast(params) + + self._set_cached_entity_analytics(cache_key, analytics) + logger.debug( + ANALYTICS_CACHE_MISS, + cache_type="entity_analytics", + workspace_id=workspace_id_str, + total_entities=analytics.total_entities, + ) + return analytics + + async def refresh_materialized_views(self) -> None: + async with self._uow_factory() as uow: + await uow.analytics.refresh_all_views() + await uow.commit() + logger.info("analytics_materialized_views_refreshed") + + async def refresh_meeting_views(self) -> None: + async with self._uow_factory() as uow: + await uow.analytics.refresh_meeting_views() + await uow.commit() + logger.info("analytics_meeting_views_refreshed") + + async def refresh_entity_views(self) -> None: + async with self._uow_factory() as uow: + await uow.analytics.refresh_entity_views() + await uow.commit() + logger.info("analytics_entity_views_refreshed") + def invalidate_cache(self, workspace_id: UUID | None = None) -> None: if workspace_id is None: self._overview_cache.clear() self._speaker_cache.clear() + self._entity_cache.clear() logger.debug("analytics_cache_cleared_all") return - keys_to_remove = [k for k in self._overview_cache if k.workspace_id == workspace_id] - for key in keys_to_remove: + overview_keys_to_remove = [ + k for k in self._overview_cache if k.workspace_id == workspace_id + ] + for key in overview_keys_to_remove: del self._overview_cache[key] - keys_to_remove = [k for k in self._speaker_cache if k.workspace_id == workspace_id] - for key in keys_to_remove: + speaker_keys_to_remove = [k for k in self._speaker_cache if k.workspace_id == workspace_id] + for key in speaker_keys_to_remove: del self._speaker_cache[key] + entity_keys_to_remove = [k for k in self._entity_cache if k.workspace_id == workspace_id] + for key in entity_keys_to_remove: + del self._entity_cache[key] + logger.debug("analytics_cache_invalidated", workspace_id=str(workspace_id)) def _get_cached_overview(self, key: _CacheKey) -> AnalyticsOverview | None: @@ -178,3 +251,22 @@ class AnalyticsService: data=data, expires_at=utc_now() + timedelta(seconds=self._cache_ttl_seconds), ) + + def _get_cached_entity_analytics(self, key: _EntityCacheKey) -> EntityAnalytics | None: + entry = self._entity_cache.get(key) + if entry is None: + return None + if utc_now() > entry.expires_at: + del self._entity_cache[key] + return None + if not isinstance(entry.data, EntityAnalytics): + return None + return entry.data + + def _set_cached_entity_analytics(self, key: _EntityCacheKey, data: EntityAnalytics) -> None: + from datetime import timedelta + + self._entity_cache[key] = _CacheEntry( + data=data, + expires_at=utc_now() + timedelta(seconds=self._cache_ttl_seconds), + ) diff --git a/src/noteflow/domain/constants/fields.py b/src/noteflow/domain/constants/fields.py index 181daff..7f99a49 100644 --- a/src/noteflow/domain/constants/fields.py +++ b/src/noteflow/domain/constants/fields.py @@ -34,6 +34,7 @@ END_TIME: Final[Literal["end_time"]] = "end_time" STATE: Final[Literal["state"]] = "state" ENDED_AT: Final[Literal["ended_at"]] = "ended_at" CODE: Final[str] = "code" +ERROR_CODE: Final[str] = "error_code" CONTENT: Final[str] = "content" LOCATION: Final[str] = "location" TASKS: Final[str] = "tasks" @@ -79,6 +80,10 @@ TOKENS_OUTPUT: Final[str] = "tokens_output" LATENCY_MS: Final[str] = "latency_ms" DURATION_MS: Final[str] = "duration_ms" +# Analytics cache event names +ANALYTICS_CACHE_HIT: Final[str] = "analytics_cache_hit" +ANALYTICS_CACHE_MISS: Final[str] = "analytics_cache_miss" + # Audio/encryption fields ASSET_PATH: Final[str] = "asset_path" WRAPPED_DEK: Final[str] = "wrapped_dek" diff --git a/src/noteflow/domain/constants/processing.py b/src/noteflow/domain/constants/processing.py new file mode 100644 index 0000000..23f468a --- /dev/null +++ b/src/noteflow/domain/constants/processing.py @@ -0,0 +1,13 @@ +"""Processing step name constants.""" + +from typing import Final + +PROCESSING_STEP_SUMMARY: Final[str] = "summary" +PROCESSING_STEP_ENTITIES: Final[str] = "entities" +PROCESSING_STEP_DIARIZATION: Final[str] = "diarization" + +__all__ = [ + "PROCESSING_STEP_DIARIZATION", + "PROCESSING_STEP_ENTITIES", + "PROCESSING_STEP_SUMMARY", +] diff --git a/src/noteflow/domain/entities/__init__.py b/src/noteflow/domain/entities/__init__.py index adf3b0d..4760ae7 100644 --- a/src/noteflow/domain/entities/__init__.py +++ b/src/noteflow/domain/entities/__init__.py @@ -1,6 +1,13 @@ """Domain entities for NoteFlow.""" -from .analytics import AnalyticsOverview, DailyMeetingStats, SpeakerStat +from .analytics import ( + AnalyticsOverview, + DailyMeetingStats, + EntityAnalytics, + EntityCategoryStat, + SpeakerStat, + TopEntity, +) from .annotation import Annotation from .integration import Integration, IntegrationStatus, IntegrationType, SyncRun, SyncRunStatus from .meeting import Meeting, MeetingCreateParams, MeetingLoadParams @@ -27,7 +34,9 @@ __all__ = [ "Annotation", "DailyMeetingStats", "EffectiveRules", + "EntityAnalytics", "EntityCategory", + "EntityCategoryStat", "ExportRules", "Integration", "IntegrationStatus", @@ -51,6 +60,7 @@ __all__ = [ "SyncRunStatus", "Task", "TaskStatus", + "TopEntity", "TriggerRules", "WordTiming", "slugify", diff --git a/src/noteflow/domain/entities/analytics.py b/src/noteflow/domain/entities/analytics.py index 124db3b..f64b3cb 100644 --- a/src/noteflow/domain/entities/analytics.py +++ b/src/noteflow/domain/entities/analytics.py @@ -70,3 +70,51 @@ class AnalyticsOverview: speaker_count: int """Number of unique speakers identified.""" + + +@dataclass(frozen=True, slots=True) +class EntityCategoryStat: + """Aggregate statistics for a single entity category.""" + + category: str + """Entity category name (person, company, location, etc.).""" + + count: int + """Total number of unique entities in this category.""" + + total_mentions: int + """Total mentions across all meetings (sum of occurrences).""" + + +@dataclass(frozen=True, slots=True) +class TopEntity: + """Most frequently mentioned entity across meetings.""" + + text: str + """Entity text (e.g., person name, company name).""" + + category: str + """Entity category (person, company, location, etc.).""" + + mention_count: int + """Number of segment mentions across all meetings.""" + + meeting_count: int + """Number of distinct meetings where this entity appears.""" + + +@dataclass(frozen=True, slots=True) +class EntityAnalytics: + """Aggregate analytics for named entities extracted from meetings.""" + + by_category: list[EntityCategoryStat] + """Entity counts grouped by category.""" + + top_entities: list[TopEntity] + """Most frequently mentioned entities.""" + + total_entities: int + """Total number of unique entities.""" + + total_mentions: int + """Total mentions across all entities and meetings.""" diff --git a/src/noteflow/domain/value_objects.py b/src/noteflow/domain/value_objects.py index 728aa19..deb3cab 100644 --- a/src/noteflow/domain/value_objects.py +++ b/src/noteflow/domain/value_objects.py @@ -21,6 +21,30 @@ MeetingId = NewType("MeetingId", UUID) AnnotationId = NewType("AnnotationId", UUID) +@dataclass(frozen=True, slots=True) +class AnalyticsQueryParams: + """Common parameters for analytics queries. + + Bundles workspace scope and time range filters used across + analytics repository methods to reduce parameter list sizes. + """ + + workspace_id: UUID + project_ids: list[UUID] | None = None + start_time: datetime | None = None + end_time: datetime | None = None + + +DEFAULT_TOP_ENTITIES_LIMIT: int = 20 + + +@dataclass(frozen=True, slots=True) +class EntityAnalyticsQueryParams(AnalyticsQueryParams): + """Parameters for entity analytics queries including top entity limit.""" + + top_limit: int = DEFAULT_TOP_ENTITIES_LIMIT + + class AnnotationType(Enum): """User annotation type. @@ -132,9 +156,7 @@ class OAuthProvider(StrEnum): try: return cls(provider.lower()) except ValueError as e: - raise ValueError( - f"Invalid provider: {provider}. Must be 'google' or 'outlook'." - ) from e + raise ValueError(f"Invalid provider: {provider}. Must be 'google' or 'outlook'.") from e @dataclass(frozen=True, slots=True) @@ -166,6 +188,7 @@ class OAuthClientConfig: redirect_uri: str scopes: tuple[str, ...] = () + @dataclass(frozen=True, slots=True) class OAuthTokens: """OAuth tokens returned from provider. diff --git a/src/noteflow/grpc/mixins/_processing_status.py b/src/noteflow/grpc/mixins/_processing_status.py index 73946d5..c65c449 100644 --- a/src/noteflow/grpc/mixins/_processing_status.py +++ b/src/noteflow/grpc/mixins/_processing_status.py @@ -4,9 +4,14 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING, Literal, cast +from typing import TYPE_CHECKING, Literal from uuid import UUID +from noteflow.domain.constants.processing import ( + PROCESSING_STEP_DIARIZATION, + PROCESSING_STEP_ENTITIES, + PROCESSING_STEP_SUMMARY, +) from noteflow.domain.entities.processing import ( ProcessingStatus, ProcessingStepState, @@ -61,26 +66,28 @@ def _apply_step_update( step: ProcessingStepName, new_state: ProcessingStepState, ) -> ProcessingStatus: - if step == "summary": + if step == PROCESSING_STEP_SUMMARY: return ProcessingStatus( summary=new_state, entities=current.entities, diarization=current.diarization, queued_at=current.queued_at, ) - if step == "entities": + if step == PROCESSING_STEP_ENTITIES: return ProcessingStatus( summary=current.summary, entities=new_state, diarization=current.diarization, queued_at=current.queued_at, ) - return ProcessingStatus( - summary=current.summary, - entities=current.entities, - diarization=new_state, - queued_at=current.queued_at, - ) + if step == PROCESSING_STEP_DIARIZATION: + return ProcessingStatus( + summary=current.summary, + entities=current.entities, + diarization=new_state, + queued_at=current.queued_at, + ) + return current async def _commit_processing_update( @@ -107,12 +114,12 @@ async def _commit_processing_update( async def _apply_processing_update( - repo_provider: Callable[[], object], + repo_provider: Callable[[], UnitOfWork], parsed_id: MeetingId, meeting_id: str, update: ProcessingStatusUpdate, ) -> bool: - async with cast(UnitOfWork, repo_provider()) as repo: + async with repo_provider() as repo: meeting = await repo.meetings.get(parsed_id) if meeting is None: logger.warning( @@ -131,7 +138,7 @@ async def _apply_processing_update( async def update_processing_status( - repo_provider: Callable[[], object], + repo_provider: Callable[[], UnitOfWork], meeting_id: str, update: ProcessingStatusUpdate, *, diff --git a/src/noteflow/grpc/mixins/analytics_mixin.py b/src/noteflow/grpc/mixins/analytics_mixin.py index ff85052..72d68cd 100644 --- a/src/noteflow/grpc/mixins/analytics_mixin.py +++ b/src/noteflow/grpc/mixins/analytics_mixin.py @@ -7,14 +7,17 @@ from uuid import UUID from typing import TYPE_CHECKING from noteflow.domain.constants.fields import PROJECT_ID -from noteflow.domain.entities.analytics import AnalyticsOverview +from noteflow.domain.entities.analytics import AnalyticsOverview, EntityAnalytics +from noteflow.domain.value_objects import EntityAnalyticsQueryParams from noteflow.infrastructure.logging import get_logger from ..proto import noteflow_pb2 from .converters import ( daily_meeting_stats_to_proto, + entity_category_stat_to_proto, parse_optional_project_ids, speaker_stat_to_proto, + top_entity_to_proto, ) from .errors import abort_database_required @@ -38,7 +41,9 @@ def _epoch_to_datetime(epoch: float) -> datetime | None: def _project_ids_from_request( - request: noteflow_pb2.GetAnalyticsOverviewRequest | noteflow_pb2.ListSpeakerStatsRequest, + request: noteflow_pb2.GetAnalyticsOverviewRequest + | noteflow_pb2.ListSpeakerStatsRequest + | noteflow_pb2.GetEntityAnalyticsRequest, ) -> list[UUID] | None: return parse_optional_project_ids( request.project_id if request.HasField(PROJECT_ID) else "", @@ -60,6 +65,17 @@ def _overview_response( ) +def _entity_analytics_response( + analytics: EntityAnalytics, +) -> noteflow_pb2.GetEntityAnalyticsResponse: + return noteflow_pb2.GetEntityAnalyticsResponse( + by_category=[entity_category_stat_to_proto(c) for c in analytics.by_category], + top_entities=[top_entity_to_proto(e) for e in analytics.top_entities], + total_entities=analytics.total_entities, + total_mentions=analytics.total_mentions, + ) + + class AnalyticsMixin: """Mixin providing analytics gRPC endpoints. @@ -131,3 +147,33 @@ class AnalyticsMixin: ) return noteflow_pb2.ListSpeakerStatsResponse(speakers=speaker_protos) + + async def GetEntityAnalytics( + self, + request: noteflow_pb2.GetEntityAnalyticsRequest, + context: GrpcContext, + ) -> noteflow_pb2.GetEntityAnalyticsResponse: + """Get aggregated entity analytics for a time range.""" + if self.analytics_service is None: + await abort_database_required(context, ENTITY_ANALYTICS) + + service = self.analytics_service + op_context = self.get_operation_context(context) + + params = EntityAnalyticsQueryParams( + workspace_id=op_context.workspace_id, + project_ids=_project_ids_from_request(request), + start_time=_epoch_to_datetime(request.start_time), + end_time=_epoch_to_datetime(request.end_time), + top_limit=request.top_limit if request.top_limit > 0 else 20, + ) + + analytics = await service.get_entity_analytics(params) + + logger.debug( + "entity_analytics_fetched", + total_entities=analytics.total_entities, + category_count=len(analytics.by_category), + ) + + return _entity_analytics_response(analytics) diff --git a/src/noteflow/grpc/mixins/converters/__init__.py b/src/noteflow/grpc/mixins/converters/__init__.py index 143e46b..ccae0f0 100644 --- a/src/noteflow/grpc/mixins/converters/__init__.py +++ b/src/noteflow/grpc/mixins/converters/__init__.py @@ -34,10 +34,12 @@ from ._domain import ( ) from ._task_analytics import ( daily_meeting_stats_to_proto, + entity_category_stat_to_proto, proto_to_task_status, speaker_stat_to_proto, task_status_to_proto, task_to_proto, + top_entity_to_proto, ) # External service converters @@ -88,6 +90,7 @@ __all__ = [ "create_vad_update", "daily_meeting_stats_to_proto", "datetime_to_epoch_seconds", + "entity_category_stat_to_proto", "datetime_to_iso_string", "datetime_to_proto_timestamp", "discovery_to_proto", @@ -122,6 +125,7 @@ __all__ = [ "sync_run_to_proto", "task_status_to_proto", "task_to_proto", + "top_entity_to_proto", "webhook_config_to_proto", "webhook_delivery_to_proto", "word_to_proto", diff --git a/src/noteflow/grpc/mixins/converters/_external.py b/src/noteflow/grpc/mixins/converters/_external.py index f4bfe1b..dc91427 100644 --- a/src/noteflow/grpc/mixins/converters/_external.py +++ b/src/noteflow/grpc/mixins/converters/_external.py @@ -77,8 +77,11 @@ def webhook_delivery_to_proto( # Sync Converters # ----------------------------------------------------------------------------- +# Type alias for proto SyncErrorCode enum values (SyncErrorCode is an int subclass) +_ProtoSyncErrorCode = noteflow_pb2.SyncErrorCode + # Map domain SyncErrorCode to proto enum values -_SYNC_ERROR_CODE_TO_PROTO: dict[SyncErrorCode, noteflow_pb2.SyncErrorCode.ValueType] = { +_SYNC_ERROR_CODE_TO_PROTO: dict[SyncErrorCode, _ProtoSyncErrorCode] = { SyncErrorCode.UNSPECIFIED: noteflow_pb2.SYNC_ERROR_CODE_UNSPECIFIED, SyncErrorCode.AUTH_REQUIRED: noteflow_pb2.SYNC_ERROR_CODE_AUTH_REQUIRED, SyncErrorCode.PROVIDER_ERROR: noteflow_pb2.SYNC_ERROR_CODE_PROVIDER_ERROR, @@ -89,7 +92,7 @@ _SYNC_ERROR_CODE_TO_PROTO: dict[SyncErrorCode, noteflow_pb2.SyncErrorCode.ValueT def sync_error_code_to_proto( error_code: SyncErrorCode | None, -) -> noteflow_pb2.SyncErrorCode.ValueType: +) -> _ProtoSyncErrorCode: """Convert domain SyncErrorCode to proto enum value. Args: @@ -100,9 +103,7 @@ def sync_error_code_to_proto( """ if error_code is None: return noteflow_pb2.SYNC_ERROR_CODE_UNSPECIFIED - return _SYNC_ERROR_CODE_TO_PROTO.get( - error_code, noteflow_pb2.SYNC_ERROR_CODE_UNKNOWN - ) + return _SYNC_ERROR_CODE_TO_PROTO.get(error_code, noteflow_pb2.SYNC_ERROR_CODE_UNKNOWN) def sync_run_to_proto(run: SyncRun) -> noteflow_pb2.SyncRunProto: diff --git a/src/noteflow/grpc/mixins/converters/_task_analytics.py b/src/noteflow/grpc/mixins/converters/_task_analytics.py index 9ae2fd0..e2c5da4 100644 --- a/src/noteflow/grpc/mixins/converters/_task_analytics.py +++ b/src/noteflow/grpc/mixins/converters/_task_analytics.py @@ -2,7 +2,12 @@ from __future__ import annotations -from noteflow.domain.entities.analytics import DailyMeetingStats, SpeakerStat +from noteflow.domain.entities.analytics import ( + DailyMeetingStats, + EntityCategoryStat, + SpeakerStat, + TopEntity, +) from noteflow.domain.entities.task import Task, TaskStatus from ...proto import noteflow_pb2 @@ -67,3 +72,24 @@ def speaker_stat_to_proto(stat: SpeakerStat) -> noteflow_pb2.SpeakerStatProto: meeting_count=stat.meeting_count, avg_confidence=stat.avg_confidence, ) + + +def entity_category_stat_to_proto( + stat: EntityCategoryStat, +) -> noteflow_pb2.EntityCategoryStatProto: + """Convert domain EntityCategoryStat to protobuf.""" + return noteflow_pb2.EntityCategoryStatProto( + category=stat.category, + count=stat.count, + total_mentions=stat.total_mentions, + ) + + +def top_entity_to_proto(entity: TopEntity) -> noteflow_pb2.TopEntityProto: + """Convert domain TopEntity to protobuf.""" + return noteflow_pb2.TopEntityProto( + text=entity.text, + category=entity.category, + mention_count=entity.mention_count, + meeting_count=entity.meeting_count, + ) diff --git a/src/noteflow/grpc/mixins/diarization/_jobs.py b/src/noteflow/grpc/mixins/diarization/_jobs.py index 921a891..3c2c0a4 100644 --- a/src/noteflow/grpc/mixins/diarization/_jobs.py +++ b/src/noteflow/grpc/mixins/diarization/_jobs.py @@ -9,6 +9,7 @@ from uuid import UUID, uuid4 import grpc from noteflow.domain.ports.unit_of_work import UnitOfWork +from noteflow.domain.constants.processing import PROCESSING_STEP_DIARIZATION from noteflow.domain.entities.processing import ProcessingStepStatus from noteflow.domain.utils import utc_now from noteflow.infrastructure.logging import get_logger, log_state_transition @@ -264,7 +265,10 @@ async def auto_trigger_diarization_refinement( await update_processing_status( host.create_repository_provider, meeting_id, - ProcessingStatusUpdate(step="diarization", status=ProcessingStepStatus.RUNNING), + ProcessingStatusUpdate( + step=PROCESSING_STEP_DIARIZATION, + status=ProcessingStepStatus.RUNNING, + ), ) _schedule_diarization_task(host, job_id, None, meeting_id) logger.info("Auto-diarization refinement triggered", meeting_id=meeting_id, job_id=job_id) @@ -284,7 +288,7 @@ class JobsMixin(JobStatusMixin): self.create_repository_provider, request.meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.FAILED, error_message=error.error_message, ), @@ -298,7 +302,7 @@ class JobsMixin(JobStatusMixin): self.create_repository_provider, request.meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.FAILED, error_message=DIARIZATION_DB_REQUIRED, ), @@ -311,7 +315,7 @@ class JobsMixin(JobStatusMixin): self.create_repository_provider, request.meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.RUNNING, ), ) diff --git a/src/noteflow/grpc/mixins/diarization/_status.py b/src/noteflow/grpc/mixins/diarization/_status.py index 008cc15..179a142 100644 --- a/src/noteflow/grpc/mixins/diarization/_status.py +++ b/src/noteflow/grpc/mixins/diarization/_status.py @@ -5,6 +5,7 @@ from __future__ import annotations from collections.abc import Callable from typing import TYPE_CHECKING, cast +from noteflow.domain.constants.processing import PROCESSING_STEP_DIARIZATION from noteflow.domain.entities.processing import ProcessingStepStatus from noteflow.infrastructure.logging import get_logger, log_state_transition from noteflow.infrastructure.persistence.repositories import DiarizationJob @@ -54,7 +55,7 @@ class JobStatusMixin: self.create_repository_provider, job.meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.COMPLETED, ), ) @@ -97,7 +98,7 @@ class JobStatusMixin: self.create_repository_provider, meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.FAILED, error_message=error_msg, ), @@ -135,7 +136,7 @@ class JobStatusMixin: self.create_repository_provider, meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.SKIPPED, error_message=ERR_CANCELLED_BY_USER, ), @@ -174,7 +175,7 @@ class JobStatusMixin: self.create_repository_provider, meeting_id, ProcessingStatusUpdate( - step="diarization", + step=PROCESSING_STEP_DIARIZATION, status=ProcessingStepStatus.FAILED, error_message=str(exc), ), diff --git a/src/noteflow/grpc/mixins/entities.py b/src/noteflow/grpc/mixins/entities.py index 1a34525..6781f62 100644 --- a/src/noteflow/grpc/mixins/entities.py +++ b/src/noteflow/grpc/mixins/entities.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, cast +from noteflow.domain.constants.processing import PROCESSING_STEP_ENTITIES from noteflow.domain.entities.processing import ProcessingStepStatus from noteflow.infrastructure.logging import get_logger @@ -54,7 +55,7 @@ class EntitiesMixin: self.create_repository_provider, meeting_id_str, ProcessingStatusUpdate( - step="entities", + step=PROCESSING_STEP_ENTITIES, status=status, error_message=error_message, ), diff --git a/src/noteflow/grpc/mixins/errors/_require.py b/src/noteflow/grpc/mixins/errors/_require.py index f662cc6..34f925e 100644 --- a/src/noteflow/grpc/mixins/errors/_require.py +++ b/src/noteflow/grpc/mixins/errors/_require.py @@ -9,6 +9,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Protocol from noteflow.config.constants import FEATURE_NAME_PROJECTS +from noteflow.config.constants.errors import ERROR_REQUIRED_SUFFIX from ...constants import WORKSPACES_LABEL from ._abort import ( @@ -238,7 +239,7 @@ async def require_field( url = await require_field(request.url, "url", context, "Webhook URL is required") """ if not value: - msg = error_message or f"{field_name} is required" + msg = error_message or f"{field_name}{ERROR_REQUIRED_SUFFIX}" await abort_invalid_argument(context, msg) return value @@ -270,7 +271,7 @@ async def require_url_field( webhook_url = await require_url_field(request.url, context) """ if not url: - await abort_invalid_argument(context, f"{field_name} is required") + await abort_invalid_argument(context, f"{field_name}{ERROR_REQUIRED_SUFFIX}") if not url.startswith(allowed_protocols): protocols_str = " or ".join(allowed_protocols) await abort_invalid_argument( diff --git a/src/noteflow/grpc/mixins/meeting/meeting_mixin.py b/src/noteflow/grpc/mixins/meeting/meeting_mixin.py index cb55fb9..3694b68 100644 --- a/src/noteflow/grpc/mixins/meeting/meeting_mixin.py +++ b/src/noteflow/grpc/mixins/meeting/meeting_mixin.py @@ -17,7 +17,7 @@ from noteflow.infrastructure.logging import get_logger from ...proto import noteflow_pb2 from ..converters import meeting_to_proto, parse_meeting_id_or_abort from ..errors import ENTITY_MEETING, abort_not_found -from ..protocols import MeetingRepositoryProvider +from ..protocols import MeetingRepositoryProvider, ServicerHost from ._post_processing import start_post_processing from ._project_scope import ( parse_project_id_or_abort, @@ -41,7 +41,6 @@ if TYPE_CHECKING: from noteflow.infrastructure.audio.writer import MeetingAudioWriter from .._types import GrpcContext - from ..protocols import ServicerHost logger = get_logger(__name__) @@ -106,7 +105,7 @@ async def _stop_meeting_and_persist(context: _StopMeetingContext) -> Meeting: ) await fire_stop_webhooks(context.host.webhook_service, context.meeting) - host_cast = cast("ServicerHost", context.host) + host_cast = cast(ServicerHost, context.host) await start_post_processing(host_cast, context.meeting_id) return context.meeting @@ -127,6 +126,19 @@ class MeetingMixin: close_audio_writer: Callable[..., None] get_operation_context: Callable[..., OperationContext] + async def _enrich_meetings_with_segments( + self, + repo: MeetingRepositoryProvider, + meetings: Sequence[Meeting], + include: bool, + ) -> Sequence[Meeting]: + if not include: + return meetings + for meeting in meetings: + segments = await repo.segments.get_by_meeting(meeting.id) + meeting.segments = list(segments) + return meetings + async def CreateMeeting( self, request: noteflow_pb2.CreateMeetingRequest, @@ -142,7 +154,7 @@ class MeetingMixin: async with cast(MeetingRepositoryProvider, self.create_repository_provider()) as repo: project_id = await resolve_create_project_id( - cast("ServicerHost", self), repo, op_context, project_id + cast(ServicerHost, self), repo, op_context, project_id ) meeting = Meeting.create( @@ -212,7 +224,7 @@ class MeetingMixin: async with cast(MeetingRepositoryProvider, self.create_repository_provider()) as repo: if project_id is None and not project_ids: - project_id = await resolve_active_project_id(cast("ServicerHost", self), repo) + project_id = await resolve_active_project_id(cast(ServicerHost, self), repo) meetings, total = await repo.meetings.list_all( states=states, @@ -222,17 +234,23 @@ class MeetingMixin: project_id=project_id, project_ids=project_ids, ) + meetings = await self._enrich_meetings_with_segments( + repo, meetings, request.include_segments + ) logger.debug( "ListMeetings returned", count=len(meetings), total=total, limit=limit, offset=offset, + include_segments=request.include_segments, project_id=str(project_id) if project_id else None, project_ids=[str(pid) for pid in project_ids] if project_ids else None, ) return noteflow_pb2.ListMeetingsResponse( - meetings=[meeting_to_proto(m, include_segments=False) for m in meetings], + meetings=[ + meeting_to_proto(m, include_segments=request.include_segments) for m in meetings + ], total_count=total, ) diff --git a/src/noteflow/grpc/mixins/webhooks.py b/src/noteflow/grpc/mixins/webhooks.py index 7966f9c..26abcd6 100644 --- a/src/noteflow/grpc/mixins/webhooks.py +++ b/src/noteflow/grpc/mixins/webhooks.py @@ -22,6 +22,7 @@ from noteflow.infrastructure.persistence.constants import ( ) from ..proto import noteflow_pb2 +from ..types import ClientErrorCode from ._types import GrpcContext from .converters import webhook_config_to_proto, webhook_delivery_to_proto from .errors import ( @@ -172,7 +173,7 @@ class WebhooksMixin: if config is None: logger.error( LOG_EVENT_WEBHOOK_UPDATE_FAILED, - reason="not_found", + reason=ClientErrorCode.NOT_FOUND.value, webhook_id=str(webhook_id), ) await abort_not_found(context, ENTITY_WEBHOOK, request.webhook_id) @@ -203,7 +204,7 @@ class WebhooksMixin: if not deleted: logger.error( LOG_EVENT_WEBHOOK_DELETE_FAILED, - reason="not_found", + reason=ClientErrorCode.NOT_FOUND.value, webhook_id=str(webhook_id), ) await abort_not_found(context, ENTITY_WEBHOOK, request.webhook_id) diff --git a/src/noteflow/grpc/proto/noteflow.proto b/src/noteflow/grpc/proto/noteflow.proto index 8fd91db..38b450f 100644 --- a/src/noteflow/grpc/proto/noteflow.proto +++ b/src/noteflow/grpc/proto/noteflow.proto @@ -142,6 +142,7 @@ service NoteFlowService { // Analytics (Bugfinder Sprint) rpc GetAnalyticsOverview(GetAnalyticsOverviewRequest) returns (GetAnalyticsOverviewResponse); rpc ListSpeakerStats(ListSpeakerStatsRequest) returns (ListSpeakerStatsResponse); + rpc GetEntityAnalytics(GetEntityAnalyticsRequest) returns (GetEntityAnalyticsResponse); // Project membership management (Sprint 18) rpc AddProjectMember(AddProjectMemberRequest) returns (ProjectMembershipProto); @@ -348,6 +349,9 @@ message ListMeetingsRequest { // Optional project filter for multiple projects (overrides project_id when provided) repeated string project_ids = 6; + + // Whether to include full transcript segments (default: false) + bool include_segments = 7; } enum SortOrder { @@ -2602,3 +2606,33 @@ message ListSpeakerStatsRequest { message ListSpeakerStatsResponse { repeated SpeakerStatProto speakers = 1; } + +// Entity Analytics (NER aggregates - Post-Sprint Bugfinder) + +message EntityCategoryStatProto { + string category = 1; + int32 count = 2; + int32 total_mentions = 3; +} + +message TopEntityProto { + string text = 1; + string category = 2; + int32 mention_count = 3; + int32 meeting_count = 4; +} + +message GetEntityAnalyticsRequest { + double start_time = 1; + double end_time = 2; + optional string project_id = 3; + repeated string project_ids = 4; + int32 top_limit = 5; +} + +message GetEntityAnalyticsResponse { + repeated EntityCategoryStatProto by_category = 1; + repeated TopEntityProto top_entities = 2; + int32 total_entities = 3; + int32 total_mentions = 4; +} diff --git a/src/noteflow/grpc/proto/noteflow_pb2.py b/src/noteflow/grpc/proto/noteflow_pb2.py index 4784d11..32f9feb 100644 --- a/src/noteflow/grpc/proto/noteflow_pb2.py +++ b/src/noteflow/grpc/proto/noteflow_pb2.py @@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0enoteflow.proto\x12\x08noteflow\"\x86\x01\n\nAudioChunk\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x12\n\naudio_data\x18\x02 \x01(\x0c\x12\x11\n\ttimestamp\x18\x03 \x01(\x01\x12\x13\n\x0bsample_rate\x18\x04 \x01(\x05\x12\x10\n\x08\x63hannels\x18\x05 \x01(\x05\x12\x16\n\x0e\x63hunk_sequence\x18\x06 \x01(\x03\"`\n\x0e\x43ongestionInfo\x12\x1b\n\x13processing_delay_ms\x18\x01 \x01(\x05\x12\x13\n\x0bqueue_depth\x18\x02 \x01(\x05\x12\x1c\n\x14throttle_recommended\x18\x03 \x01(\x08\"\x98\x02\n\x10TranscriptUpdate\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12)\n\x0bupdate_type\x18\x02 \x01(\x0e\x32\x14.noteflow.UpdateType\x12\x14\n\x0cpartial_text\x18\x03 \x01(\t\x12\'\n\x07segment\x18\x04 \x01(\x0b\x32\x16.noteflow.FinalSegment\x12\x18\n\x10server_timestamp\x18\x05 \x01(\x01\x12\x19\n\x0c\x61\x63k_sequence\x18\x06 \x01(\x03H\x00\x88\x01\x01\x12\x31\n\ncongestion\x18\n \x01(\x0b\x32\x18.noteflow.CongestionInfoH\x01\x88\x01\x01\x42\x0f\n\r_ack_sequenceB\r\n\x0b_congestion\"\x87\x02\n\x0c\x46inalSegment\x12\x12\n\nsegment_id\x18\x01 \x01(\x05\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x01\x12#\n\x05words\x18\x05 \x03(\x0b\x32\x14.noteflow.WordTiming\x12\x10\n\x08language\x18\x06 \x01(\t\x12\x1b\n\x13language_confidence\x18\x07 \x01(\x02\x12\x13\n\x0b\x61vg_logprob\x18\x08 \x01(\x02\x12\x16\n\x0eno_speech_prob\x18\t \x01(\x02\x12\x12\n\nspeaker_id\x18\n \x01(\t\x12\x1a\n\x12speaker_confidence\x18\x0b \x01(\x02\"U\n\nWordTiming\x12\x0c\n\x04word\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x01\x12\x13\n\x0bprobability\x18\x04 \x01(\x02\"\xb0\x03\n\x07Meeting\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12%\n\x05state\x18\x03 \x01(\x0e\x32\x16.noteflow.MeetingState\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\x12\n\nstarted_at\x18\x05 \x01(\x01\x12\x10\n\x08\x65nded_at\x18\x06 \x01(\x01\x12\x18\n\x10\x64uration_seconds\x18\x07 \x01(\x01\x12(\n\x08segments\x18\x08 \x03(\x0b\x32\x16.noteflow.FinalSegment\x12\"\n\x07summary\x18\t \x01(\x0b\x32\x11.noteflow.Summary\x12\x31\n\x08metadata\x18\n \x03(\x0b\x32\x1f.noteflow.Meeting.MetadataEntry\x12\x17\n\nproject_id\x18\x0b \x01(\tH\x00\x88\x01\x01\x12\x35\n\x11processing_status\x18\x0c \x01(\x0b\x32\x1a.noteflow.ProcessingStatus\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_project_id\"\xbe\x01\n\x14\x43reateMeetingRequest\x12\r\n\x05title\x18\x01 \x01(\t\x12>\n\x08metadata\x18\x02 \x03(\x0b\x32,.noteflow.CreateMeetingRequest.MetadataEntry\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_project_id\"(\n\x12StopMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\"\xc2\x01\n\x13ListMeetingsRequest\x12&\n\x06states\x18\x01 \x03(\x0e\x32\x16.noteflow.MeetingState\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\'\n\nsort_order\x18\x04 \x01(\x0e\x32\x13.noteflow.SortOrder\x12\x17\n\nproject_id\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x06 \x03(\tB\r\n\x0b_project_id\"P\n\x14ListMeetingsResponse\x12#\n\x08meetings\x18\x01 \x03(\x0b\x32\x11.noteflow.Meeting\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"Z\n\x11GetMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x18\n\x10include_segments\x18\x02 \x01(\x08\x12\x17\n\x0finclude_summary\x18\x03 \x01(\x08\"*\n\x14\x44\x65leteMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteMeetingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xb9\x01\n\x07Summary\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x19\n\x11\x65xecutive_summary\x18\x02 \x01(\t\x12&\n\nkey_points\x18\x03 \x03(\x0b\x32\x12.noteflow.KeyPoint\x12*\n\x0c\x61\x63tion_items\x18\x04 \x03(\x0b\x32\x14.noteflow.ActionItem\x12\x14\n\x0cgenerated_at\x18\x05 \x01(\x01\x12\x15\n\rmodel_version\x18\x06 \x01(\t\"S\n\x08KeyPoint\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x13\n\x0bsegment_ids\x18\x02 \x03(\x05\x12\x12\n\nstart_time\x18\x03 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x01\"y\n\nActionItem\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x10\n\x08\x61ssignee\x18\x02 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x03 \x01(\x01\x12$\n\x08priority\x18\x04 \x01(\x0e\x32\x12.noteflow.Priority\x12\x13\n\x0bsegment_ids\x18\x05 \x03(\x05\"\\\n\x14SummarizationOptions\x12\x0c\n\x04tone\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x11\n\tverbosity\x18\x03 \x01(\t\x12\x13\n\x0btemplate_id\x18\x04 \x01(\t\"w\n\x16GenerateSummaryRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x18\n\x10\x66orce_regenerate\x18\x02 \x01(\x08\x12/\n\x07options\x18\x03 \x01(\x0b\x32\x1e.noteflow.SummarizationOptions\"\xe4\x02\n\x1aSummarizationTemplateProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x19\n\x0cworkspace_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x11\n\tis_system\x18\x05 \x01(\x08\x12\x13\n\x0bis_archived\x18\x06 \x01(\x08\x12\x1f\n\x12\x63urrent_version_id\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x08 \x01(\x03\x12\x12\n\nupdated_at\x18\t \x01(\x03\x12\x17\n\ncreated_by\x18\n \x01(\tH\x03\x88\x01\x01\x12\x17\n\nupdated_by\x18\x0b \x01(\tH\x04\x88\x01\x01\x42\x0f\n\r_workspace_idB\x0e\n\x0c_descriptionB\x15\n\x13_current_version_idB\r\n\x0b_created_byB\r\n\x0b_updated_by\"\xd3\x01\n!SummarizationTemplateVersionProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0btemplate_id\x18\x02 \x01(\t\x12\x16\n\x0eversion_number\x18\x03 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x12\n\ncreated_at\x18\x06 \x01(\x03\x12\x17\n\ncreated_by\x18\x07 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_change_noteB\r\n\x0b_created_by\"\x8a\x01\n!ListSummarizationTemplatesRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x16\n\x0einclude_system\x18\x02 \x01(\x08\x12\x18\n\x10include_archived\x18\x03 \x01(\x08\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\"r\n\"ListSummarizationTemplatesResponse\x12\x37\n\ttemplates\x18\x01 \x03(\x0b\x32$.noteflow.SummarizationTemplateProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"W\n\x1fGetSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x1f\n\x17include_current_version\x18\x02 \x01(\x08\"\xb9\x01\n GetSummarizationTemplateResponse\x12\x36\n\x08template\x18\x01 \x01(\x0b\x32$.noteflow.SummarizationTemplateProto\x12I\n\x0f\x63urrent_version\x18\x02 \x01(\x0b\x32+.noteflow.SummarizationTemplateVersionProtoH\x00\x88\x01\x01\x42\x12\n\x10_current_version\"\xae\x01\n%SummarizationTemplateMutationResponse\x12\x36\n\x08template\x18\x01 \x01(\x0b\x32$.noteflow.SummarizationTemplateProto\x12\x41\n\x07version\x18\x02 \x01(\x0b\x32+.noteflow.SummarizationTemplateVersionProtoH\x00\x88\x01\x01\x42\n\n\x08_version\"\xad\x01\n\"CreateSummarizationTemplateRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_descriptionB\x0e\n\x0c_change_note\"\xcb\x01\n\"UpdateSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x14\n\x07\x63ontent\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x03\x88\x01\x01\x42\x07\n\x05_nameB\x0e\n\x0c_descriptionB\n\n\x08_contentB\x0e\n\x0c_change_note\":\n#ArchiveSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\"^\n(ListSummarizationTemplateVersionsRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\x7f\n)ListSummarizationTemplateVersionsResponse\x12=\n\x08versions\x18\x01 \x03(\x0b\x32+.noteflow.SummarizationTemplateVersionProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"U\n*RestoreSummarizationTemplateVersionRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x12\n\nversion_id\x18\x02 \x01(\t\"\x13\n\x11ServerInfoRequest\"\x83\x04\n\nServerInfo\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x11\n\tasr_model\x18\x02 \x01(\t\x12\x11\n\tasr_ready\x18\x03 \x01(\x08\x12\x1e\n\x16supported_sample_rates\x18\x04 \x03(\x05\x12\x16\n\x0emax_chunk_size\x18\x05 \x01(\x05\x12\x16\n\x0euptime_seconds\x18\x06 \x01(\x01\x12\x17\n\x0f\x61\x63tive_meetings\x18\x07 \x01(\x05\x12\x1b\n\x13\x64iarization_enabled\x18\x08 \x01(\x08\x12\x19\n\x11\x64iarization_ready\x18\t \x01(\x08\x12\x15\n\rstate_version\x18\n \x01(\x03\x12#\n\x16system_ram_total_bytes\x18\x0b \x01(\x03H\x00\x88\x01\x01\x12\'\n\x1asystem_ram_available_bytes\x18\x0c \x01(\x03H\x01\x88\x01\x01\x12!\n\x14gpu_vram_total_bytes\x18\r \x01(\x03H\x02\x88\x01\x01\x12%\n\x18gpu_vram_available_bytes\x18\x0e \x01(\x03H\x03\x88\x01\x01\x42\x19\n\x17_system_ram_total_bytesB\x1d\n\x1b_system_ram_available_bytesB\x17\n\x15_gpu_vram_total_bytesB\x1b\n\x19_gpu_vram_available_bytes\"\xac\x02\n\x10\x41srConfiguration\x12\x12\n\nmodel_size\x18\x01 \x01(\t\x12#\n\x06\x64\x65vice\x18\x02 \x01(\x0e\x32\x13.noteflow.AsrDevice\x12.\n\x0c\x63ompute_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AsrComputeType\x12\x10\n\x08is_ready\x18\x04 \x01(\x08\x12\x16\n\x0e\x63uda_available\x18\x05 \x01(\x08\x12\x1d\n\x15\x61vailable_model_sizes\x18\x06 \x03(\t\x12\x39\n\x17\x61vailable_compute_types\x18\x07 \x03(\x0e\x32\x18.noteflow.AsrComputeType\x12\x16\n\x0erocm_available\x18\x08 \x01(\x08\x12\x13\n\x0bgpu_backend\x18\t \x01(\t\"\x1c\n\x1aGetAsrConfigurationRequest\"P\n\x1bGetAsrConfigurationResponse\x12\x31\n\rconfiguration\x18\x01 \x01(\x0b\x32\x1a.noteflow.AsrConfiguration\"\xc2\x01\n\x1dUpdateAsrConfigurationRequest\x12\x17\n\nmodel_size\x18\x01 \x01(\tH\x00\x88\x01\x01\x12(\n\x06\x64\x65vice\x18\x02 \x01(\x0e\x32\x13.noteflow.AsrDeviceH\x01\x88\x01\x01\x12\x33\n\x0c\x63ompute_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AsrComputeTypeH\x02\x88\x01\x01\x42\r\n\x0b_model_sizeB\t\n\x07_deviceB\x0f\n\r_compute_type\"~\n\x1eUpdateAsrConfigurationResponse\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x04 \x01(\x08\"5\n#GetAsrConfigurationJobStatusRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"\xe2\x01\n\x19\x41srConfigurationJobStatus\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x18\n\x10progress_percent\x18\x03 \x01(\x02\x12\r\n\x05phase\x18\x04 \x01(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12:\n\x11new_configuration\x18\x06 \x01(\x0b\x32\x1a.noteflow.AsrConfigurationH\x00\x88\x01\x01\x42\x14\n\x12_new_configuration\"\xe9\x01\n\x16StreamingConfiguration\x12\x1f\n\x17partial_cadence_seconds\x18\x01 \x01(\x02\x12!\n\x19min_partial_audio_seconds\x18\x02 \x01(\x02\x12$\n\x1cmax_segment_duration_seconds\x18\x03 \x01(\x02\x12#\n\x1bmin_speech_duration_seconds\x18\x04 \x01(\x02\x12 \n\x18trailing_silence_seconds\x18\x05 \x01(\x02\x12\x1e\n\x16leading_buffer_seconds\x18\x06 \x01(\x02\"\"\n GetStreamingConfigurationRequest\"\\\n!GetStreamingConfigurationResponse\x12\x37\n\rconfiguration\x18\x01 \x01(\x0b\x32 .noteflow.StreamingConfiguration\"\xc7\x03\n#UpdateStreamingConfigurationRequest\x12$\n\x17partial_cadence_seconds\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12&\n\x19min_partial_audio_seconds\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12)\n\x1cmax_segment_duration_seconds\x18\x03 \x01(\x02H\x02\x88\x01\x01\x12(\n\x1bmin_speech_duration_seconds\x18\x04 \x01(\x02H\x03\x88\x01\x01\x12%\n\x18trailing_silence_seconds\x18\x05 \x01(\x02H\x04\x88\x01\x01\x12#\n\x16leading_buffer_seconds\x18\x06 \x01(\x02H\x05\x88\x01\x01\x42\x1a\n\x18_partial_cadence_secondsB\x1c\n\x1a_min_partial_audio_secondsB\x1f\n\x1d_max_segment_duration_secondsB\x1e\n\x1c_min_speech_duration_secondsB\x1b\n\x19_trailing_silence_secondsB\x19\n\x17_leading_buffer_seconds\"_\n$UpdateStreamingConfigurationResponse\x12\x37\n\rconfiguration\x18\x01 \x01(\x0b\x32 .noteflow.StreamingConfiguration\"\xbc\x01\n\nAnnotation\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nmeeting_id\x18\x02 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nstart_time\x18\x05 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x06 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x07 \x03(\x05\x12\x12\n\ncreated_at\x18\x08 \x01(\x01\"\xa6\x01\n\x14\x41\x64\x64\x41nnotationRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x02 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x12\n\nstart_time\x18\x04 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x05 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x06 \x03(\x05\"-\n\x14GetAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\"R\n\x16ListAnnotationsRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x01\"D\n\x17ListAnnotationsResponse\x12)\n\x0b\x61nnotations\x18\x01 \x03(\x0b\x32\x14.noteflow.Annotation\"\xac\x01\n\x17UpdateAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x02 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x12\n\nstart_time\x18\x04 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x05 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x06 \x03(\x05\"0\n\x17\x44\x65leteAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\"+\n\x18\x44\x65leteAnnotationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\x86\x01\n\x13ProcessingStepState\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.noteflow.ProcessingStepStatus\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x12\n\nstarted_at\x18\x03 \x01(\x01\x12\x14\n\x0c\x63ompleted_at\x18\x04 \x01(\x01\"\xa7\x01\n\x10ProcessingStatus\x12.\n\x07summary\x18\x01 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\x12/\n\x08\x65ntities\x18\x02 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\x12\x32\n\x0b\x64iarization\x18\x03 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\"U\n\x17\x45xportTranscriptRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12&\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x16.noteflow.ExportFormat\"X\n\x18\x45xportTranscriptResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x13\n\x0b\x66ormat_name\x18\x02 \x01(\t\x12\x16\n\x0e\x66ile_extension\x18\x03 \x01(\t\"K\n\x1fRefineSpeakerDiarizationRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x14\n\x0cnum_speakers\x18\x02 \x01(\x05\"\x9d\x01\n RefineSpeakerDiarizationResponse\x12\x18\n\x10segments_updated\x18\x01 \x01(\x05\x12\x13\n\x0bspeaker_ids\x18\x02 \x03(\t\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x0e\n\x06job_id\x18\x04 \x01(\t\x12#\n\x06status\x18\x05 \x01(\x0e\x32\x13.noteflow.JobStatus\"\\\n\x14RenameSpeakerRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x16\n\x0eold_speaker_id\x18\x02 \x01(\t\x12\x18\n\x10new_speaker_name\x18\x03 \x01(\t\"B\n\x15RenameSpeakerResponse\x12\x18\n\x10segments_updated\x18\x01 \x01(\x05\x12\x0f\n\x07success\x18\x02 \x01(\x08\"0\n\x1eGetDiarizationJobStatusRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"\xab\x01\n\x14\x44iarizationJobStatus\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x18\n\x10segments_updated\x18\x03 \x01(\x05\x12\x13\n\x0bspeaker_ids\x18\x04 \x03(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x18\n\x10progress_percent\x18\x06 \x01(\x02\"-\n\x1b\x43\x61ncelDiarizationJobRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"k\n\x1c\x43\x61ncelDiarizationJobResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.noteflow.JobStatus\"!\n\x1fGetActiveDiarizationJobsRequest\"P\n GetActiveDiarizationJobsResponse\x12,\n\x04jobs\x18\x01 \x03(\x0b\x32\x1e.noteflow.DiarizationJobStatus\"C\n\x16\x45xtractEntitiesRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x15\n\rforce_refresh\x18\x02 \x01(\x08\"y\n\x0f\x45xtractedEntity\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x03 \x01(\t\x12\x13\n\x0bsegment_ids\x18\x04 \x03(\x05\x12\x12\n\nconfidence\x18\x05 \x01(\x02\x12\x11\n\tis_pinned\x18\x06 \x01(\x08\"k\n\x17\x45xtractEntitiesResponse\x12+\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x19.noteflow.ExtractedEntity\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x61\x63hed\x18\x03 \x01(\x08\"\\\n\x13UpdateEntityRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x11\n\tentity_id\x18\x02 \x01(\t\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x04 \x01(\t\"A\n\x14UpdateEntityResponse\x12)\n\x06\x65ntity\x18\x01 \x01(\x0b\x32\x19.noteflow.ExtractedEntity\"<\n\x13\x44\x65leteEntityRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x11\n\tentity_id\x18\x02 \x01(\t\"\'\n\x14\x44\x65leteEntityResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xc7\x01\n\rCalendarEvent\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x03\x12\x11\n\tattendees\x18\x05 \x03(\t\x12\x10\n\x08location\x18\x06 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x13\n\x0bmeeting_url\x18\x08 \x01(\t\x12\x14\n\x0cis_recurring\x18\t \x01(\x08\x12\x10\n\x08provider\x18\n \x01(\t\"Q\n\x19ListCalendarEventsRequest\x12\x13\n\x0bhours_ahead\x18\x01 \x01(\x05\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x10\n\x08provider\x18\x03 \x01(\t\"Z\n\x1aListCalendarEventsResponse\x12\'\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x17.noteflow.CalendarEvent\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x1d\n\x1bGetCalendarProvidersRequest\"P\n\x10\x43\x61lendarProvider\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x18\n\x10is_authenticated\x18\x02 \x01(\x08\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\"M\n\x1cGetCalendarProvidersResponse\x12-\n\tproviders\x18\x01 \x03(\x0b\x32\x1a.noteflow.CalendarProvider\"X\n\x14InitiateOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x14\n\x0credirect_uri\x18\x02 \x01(\t\x12\x18\n\x10integration_type\x18\x03 \x01(\t\"8\n\x15InitiateOAuthResponse\x12\x10\n\x08\x61uth_url\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"E\n\x14\x43ompleteOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\"o\n\x15\x43ompleteOAuthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x16\n\x0eprovider_email\x18\x03 \x01(\t\x12\x16\n\x0eintegration_id\x18\x04 \x01(\t\"\x87\x01\n\x0fOAuthConnection\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x18\n\x10integration_type\x18\x06 \x01(\t\"M\n\x1fGetOAuthConnectionStatusRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\"Q\n GetOAuthConnectionStatusResponse\x12-\n\nconnection\x18\x01 \x01(\x0b\x32\x19.noteflow.OAuthConnection\"D\n\x16\x44isconnectOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\"A\n\x17\x44isconnectOAuthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\"\xaf\x01\n\x11OAuthClientConfig\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12\x1a\n\rclient_secret\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0credirect_uri\x18\x03 \x01(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\x12\x18\n\x10override_enabled\x18\x05 \x01(\x08\x12\x19\n\x11has_client_secret\x18\x06 \x01(\x08\x42\x10\n\x0e_client_secret\"_\n\x1bGetOAuthClientConfigRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x03 \x01(\t\"K\n\x1cGetOAuthClientConfigResponse\x12+\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1b.noteflow.OAuthClientConfig\"\x8c\x01\n\x1bSetOAuthClientConfigRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x03 \x01(\t\x12+\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x1b.noteflow.OAuthClientConfig\"/\n\x1cSetOAuthClientConfigResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\x92\x01\n\x16RegisterWebhookRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06secret\x18\x05 \x01(\t\x12\x12\n\ntimeout_ms\x18\x06 \x01(\x05\x12\x13\n\x0bmax_retries\x18\x07 \x01(\x05\"\xc3\x01\n\x12WebhookConfigProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0b\n\x03url\x18\x04 \x01(\t\x12\x0e\n\x06\x65vents\x18\x05 \x03(\t\x12\x0f\n\x07\x65nabled\x18\x06 \x01(\x08\x12\x12\n\ntimeout_ms\x18\x07 \x01(\x05\x12\x13\n\x0bmax_retries\x18\x08 \x01(\x05\x12\x12\n\ncreated_at\x18\t \x01(\x03\x12\x12\n\nupdated_at\x18\n \x01(\x03\"+\n\x13ListWebhooksRequest\x12\x14\n\x0c\x65nabled_only\x18\x01 \x01(\x08\"[\n\x14ListWebhooksResponse\x12.\n\x08webhooks\x18\x01 \x03(\x0b\x32\x1c.noteflow.WebhookConfigProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x84\x02\n\x14UpdateWebhookRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\x12\x10\n\x03url\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\x12\x11\n\x04name\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06secret\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x14\n\x07\x65nabled\x18\x06 \x01(\x08H\x03\x88\x01\x01\x12\x17\n\ntimeout_ms\x18\x07 \x01(\x05H\x04\x88\x01\x01\x12\x18\n\x0bmax_retries\x18\x08 \x01(\x05H\x05\x88\x01\x01\x42\x06\n\x04_urlB\x07\n\x05_nameB\t\n\x07_secretB\n\n\x08_enabledB\r\n\x0b_timeout_msB\x0e\n\x0c_max_retries\"*\n\x14\x44\x65leteWebhookRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteWebhookResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xcb\x01\n\x14WebhookDeliveryProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nwebhook_id\x18\x02 \x01(\t\x12\x12\n\nevent_type\x18\x03 \x01(\t\x12\x13\n\x0bstatus_code\x18\x04 \x01(\x05\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x15\n\rattempt_count\x18\x06 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x07 \x01(\x05\x12\x14\n\x0c\x64\x65livered_at\x18\x08 \x01(\x03\x12\x11\n\tsucceeded\x18\t \x01(\x08\"@\n\x1bGetWebhookDeliveriesRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\"g\n\x1cGetWebhookDeliveriesResponse\x12\x32\n\ndeliveries\x18\x01 \x03(\x0b\x32\x1e.noteflow.WebhookDeliveryProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x1a\n\x18GrantCloudConsentRequest\"\x1b\n\x19GrantCloudConsentResponse\"\x1b\n\x19RevokeCloudConsentRequest\"\x1c\n\x1aRevokeCloudConsentResponse\"\x1e\n\x1cGetCloudConsentStatusRequest\"8\n\x1dGetCloudConsentStatusResponse\x12\x17\n\x0f\x63onsent_granted\x18\x01 \x01(\x08\"=\n\x1aSetHuggingFaceTokenRequest\x12\r\n\x05token\x18\x01 \x01(\t\x12\x10\n\x08validate\x18\x02 \x01(\x08\"x\n\x1bSetHuggingFaceTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x12\n\x05valid\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x18\n\x10validation_error\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\tB\x08\n\x06_valid\"\"\n GetHuggingFaceTokenStatusRequest\"x\n!GetHuggingFaceTokenStatusResponse\x12\x15\n\ris_configured\x18\x01 \x01(\x08\x12\x14\n\x0cis_validated\x18\x02 \x01(\x08\x12\x10\n\x08username\x18\x03 \x01(\t\x12\x14\n\x0cvalidated_at\x18\x04 \x01(\x01\"\x1f\n\x1d\x44\x65leteHuggingFaceTokenRequest\"1\n\x1e\x44\x65leteHuggingFaceTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"!\n\x1fValidateHuggingFaceTokenRequest\"Z\n ValidateHuggingFaceTokenResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x15\n\rerror_message\x18\x03 \x01(\t\"%\n\x15GetPreferencesRequest\x12\x0c\n\x04keys\x18\x01 \x03(\t\"\xb6\x01\n\x16GetPreferencesResponse\x12\x46\n\x0bpreferences\x18\x01 \x03(\x0b\x32\x31.noteflow.GetPreferencesResponse.PreferencesEntry\x12\x12\n\nupdated_at\x18\x02 \x01(\x01\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x1a\x32\n\x10PreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xce\x01\n\x15SetPreferencesRequest\x12\x45\n\x0bpreferences\x18\x01 \x03(\x0b\x32\x30.noteflow.SetPreferencesRequest.PreferencesEntry\x12\x10\n\x08if_match\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_updated_at\x18\x03 \x01(\x01\x12\r\n\x05merge\x18\x04 \x01(\x08\x1a\x32\n\x10PreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8d\x02\n\x16SetPreferencesResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x10\n\x08\x63onflict\x18\x02 \x01(\x08\x12S\n\x12server_preferences\x18\x03 \x03(\x0b\x32\x37.noteflow.SetPreferencesResponse.ServerPreferencesEntry\x12\x19\n\x11server_updated_at\x18\x04 \x01(\x01\x12\x0c\n\x04\x65tag\x18\x05 \x01(\t\x12\x18\n\x10\x63onflict_message\x18\x06 \x01(\t\x1a\x38\n\x16ServerPreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"5\n\x1bStartIntegrationSyncRequest\x12\x16\n\x0eintegration_id\x18\x01 \x01(\t\"C\n\x1cStartIntegrationSyncResponse\x12\x13\n\x0bsync_run_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t\"+\n\x14GetSyncStatusRequest\x12\x13\n\x0bsync_run_id\x18\x01 \x01(\t\"\xf0\x01\n\x15GetSyncStatusResponse\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x14\n\x0citems_synced\x18\x02 \x01(\x05\x12\x13\n\x0bitems_total\x18\x03 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x05 \x01(\x03\x12+\n\nerror_code\x18\x06 \x01(\x0e\x32\x17.noteflow.SyncErrorCode\x12\x17\n\nexpires_at\x18\n \x01(\tH\x00\x88\x01\x01\x12\x1d\n\x10not_found_reason\x18\x0b \x01(\tH\x01\x88\x01\x01\x42\r\n\x0b_expires_atB\x13\n\x11_not_found_reason\"O\n\x16ListSyncHistoryRequest\x12\x16\n\x0eintegration_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"T\n\x17ListSyncHistoryResponse\x12$\n\x04runs\x18\x01 \x03(\x0b\x32\x16.noteflow.SyncRunProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xc4\x01\n\x0cSyncRunProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x16\n\x0eintegration_id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x14\n\x0citems_synced\x18\x04 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x06 \x01(\x03\x12\x12\n\nstarted_at\x18\x07 \x01(\t\x12\x14\n\x0c\x63ompleted_at\x18\x08 \x01(\t\x12+\n\nerror_code\x18\t \x01(\x0e\x32\x17.noteflow.SyncErrorCode\"\x1c\n\x1aGetUserIntegrationsRequest\"_\n\x0fIntegrationInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x05 \x01(\t\"N\n\x1bGetUserIntegrationsResponse\x12/\n\x0cintegrations\x18\x01 \x03(\x0b\x32\x19.noteflow.IntegrationInfo\"D\n\x14GetRecentLogsRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\r\n\x05level\x18\x02 \x01(\t\x12\x0e\n\x06source\x18\x03 \x01(\t\">\n\x15GetRecentLogsResponse\x12%\n\x04logs\x18\x01 \x03(\x0b\x32\x17.noteflow.LogEntryProto\"\x99\x02\n\rLogEntryProto\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\r\n\x05level\x18\x02 \x01(\t\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\t\x12\x35\n\x07\x64\x65tails\x18\x05 \x03(\x0b\x32$.noteflow.LogEntryProto.DetailsEntry\x12\x10\n\x08trace_id\x18\x06 \x01(\t\x12\x0f\n\x07span_id\x18\x07 \x01(\t\x12\x12\n\nevent_type\x18\x08 \x01(\t\x12\x14\n\x0coperation_id\x18\t \x01(\t\x12\x11\n\tentity_id\x18\n \x01(\t\x1a.\n\x0c\x44\x65tailsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"5\n\x1cGetPerformanceMetricsRequest\x12\x15\n\rhistory_limit\x18\x01 \x01(\x05\"\x87\x01\n\x1dGetPerformanceMetricsResponse\x12\x32\n\x07\x63urrent\x18\x01 \x01(\x0b\x32!.noteflow.PerformanceMetricsPoint\x12\x32\n\x07history\x18\x02 \x03(\x0b\x32!.noteflow.PerformanceMetricsPoint\"\xf1\x01\n\x17PerformanceMetricsPoint\x12\x11\n\ttimestamp\x18\x01 \x01(\x01\x12\x13\n\x0b\x63pu_percent\x18\x02 \x01(\x01\x12\x16\n\x0ememory_percent\x18\x03 \x01(\x01\x12\x11\n\tmemory_mb\x18\x04 \x01(\x01\x12\x14\n\x0c\x64isk_percent\x18\x05 \x01(\x01\x12\x1a\n\x12network_bytes_sent\x18\x06 \x01(\x03\x12\x1a\n\x12network_bytes_recv\x18\x07 \x01(\x03\x12\x19\n\x11process_memory_mb\x18\x08 \x01(\x01\x12\x1a\n\x12\x61\x63tive_connections\x18\t \x01(\x05\"\xd0\x02\n\x11\x43laimMappingProto\x12\x15\n\rsubject_claim\x18\x01 \x01(\t\x12\x13\n\x0b\x65mail_claim\x18\x02 \x01(\t\x12\x1c\n\x14\x65mail_verified_claim\x18\x03 \x01(\t\x12\x12\n\nname_claim\x18\x04 \x01(\t\x12 \n\x18preferred_username_claim\x18\x05 \x01(\t\x12\x14\n\x0cgroups_claim\x18\x06 \x01(\t\x12\x15\n\rpicture_claim\x18\x07 \x01(\t\x12\x1d\n\x10\x66irst_name_claim\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0flast_name_claim\x18\t \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bphone_claim\x18\n \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_first_name_claimB\x12\n\x10_last_name_claimB\x0e\n\x0c_phone_claim\"\xf7\x02\n\x12OidcDiscoveryProto\x12\x0e\n\x06issuer\x18\x01 \x01(\t\x12\x1e\n\x16\x61uthorization_endpoint\x18\x02 \x01(\t\x12\x16\n\x0etoken_endpoint\x18\x03 \x01(\t\x12\x1e\n\x11userinfo_endpoint\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08jwks_uri\x18\x05 \x01(\tH\x01\x88\x01\x01\x12!\n\x14\x65nd_session_endpoint\x18\x06 \x01(\tH\x02\x88\x01\x01\x12 \n\x13revocation_endpoint\x18\x07 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x10scopes_supported\x18\x08 \x03(\t\x12\x18\n\x10\x63laims_supported\x18\t \x03(\t\x12\x15\n\rsupports_pkce\x18\n \x01(\x08\x42\x14\n\x12_userinfo_endpointB\x0b\n\t_jwks_uriB\x17\n\x15_end_session_endpointB\x16\n\x14_revocation_endpoint\"\xc5\x03\n\x11OidcProviderProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06preset\x18\x04 \x01(\t\x12\x12\n\nissuer_url\x18\x05 \x01(\t\x12\x11\n\tclient_id\x18\x06 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x07 \x01(\x08\x12\x34\n\tdiscovery\x18\x08 \x01(\x0b\x32\x1c.noteflow.OidcDiscoveryProtoH\x00\x88\x01\x01\x12\x32\n\rclaim_mapping\x18\t \x01(\x0b\x32\x1b.noteflow.ClaimMappingProto\x12\x0e\n\x06scopes\x18\n \x03(\t\x12\x1e\n\x16require_email_verified\x18\x0b \x01(\x08\x12\x16\n\x0e\x61llowed_groups\x18\x0c \x03(\t\x12\x12\n\ncreated_at\x18\r \x01(\x03\x12\x12\n\nupdated_at\x18\x0e \x01(\x03\x12#\n\x16\x64iscovery_refreshed_at\x18\x0f \x01(\x03H\x01\x88\x01\x01\x12\x10\n\x08warnings\x18\x10 \x03(\tB\x0c\n\n_discoveryB\x19\n\x17_discovery_refreshed_at\"\xf0\x02\n\x1bRegisterOidcProviderRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\nissuer_url\x18\x03 \x01(\t\x12\x11\n\tclient_id\x18\x04 \x01(\t\x12\x1a\n\rclient_secret\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06preset\x18\x06 \x01(\t\x12\x0e\n\x06scopes\x18\x07 \x03(\t\x12\x37\n\rclaim_mapping\x18\x08 \x01(\x0b\x32\x1b.noteflow.ClaimMappingProtoH\x01\x88\x01\x01\x12\x16\n\x0e\x61llowed_groups\x18\t \x03(\t\x12#\n\x16require_email_verified\x18\n \x01(\x08H\x02\x88\x01\x01\x12\x15\n\rauto_discover\x18\x0b \x01(\x08\x42\x10\n\x0e_client_secretB\x10\n\x0e_claim_mappingB\x19\n\x17_require_email_verified\"\\\n\x18ListOidcProvidersRequest\x12\x19\n\x0cworkspace_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0c\x65nabled_only\x18\x02 \x01(\x08\x42\x0f\n\r_workspace_id\"`\n\x19ListOidcProvidersResponse\x12.\n\tproviders\x18\x01 \x03(\x0b\x32\x1b.noteflow.OidcProviderProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"-\n\x16GetOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"\xa1\x02\n\x19UpdateOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06scopes\x18\x03 \x03(\t\x12\x37\n\rclaim_mapping\x18\x04 \x01(\x0b\x32\x1b.noteflow.ClaimMappingProtoH\x01\x88\x01\x01\x12\x16\n\x0e\x61llowed_groups\x18\x05 \x03(\t\x12#\n\x16require_email_verified\x18\x06 \x01(\x08H\x02\x88\x01\x01\x12\x14\n\x07\x65nabled\x18\x07 \x01(\x08H\x03\x88\x01\x01\x42\x07\n\x05_nameB\x10\n\x0e_claim_mappingB\x19\n\x17_require_email_verifiedB\n\n\x08_enabled\"0\n\x19\x44\x65leteOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"-\n\x1a\x44\x65leteOidcProviderResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"s\n\x1bRefreshOidcDiscoveryRequest\x12\x18\n\x0bprovider_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cworkspace_id\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_provider_idB\x0f\n\r_workspace_id\"\xc2\x01\n\x1cRefreshOidcDiscoveryResponse\x12\x44\n\x07results\x18\x01 \x03(\x0b\x32\x33.noteflow.RefreshOidcDiscoveryResponse.ResultsEntry\x12\x15\n\rsuccess_count\x18\x02 \x01(\x05\x12\x15\n\rfailure_count\x18\x03 \x01(\x05\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x18\n\x16ListOidcPresetsRequest\"\xb8\x01\n\x0fOidcPresetProto\x12\x0e\n\x06preset\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x16\n\x0e\x64\x65\x66\x61ult_scopes\x18\x04 \x03(\t\x12\x1e\n\x11\x64ocumentation_url\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05notes\x18\x06 \x01(\tH\x01\x88\x01\x01\x42\x14\n\x12_documentation_urlB\x08\n\x06_notes\"E\n\x17ListOidcPresetsResponse\x12*\n\x07presets\x18\x01 \x03(\x0b\x32\x19.noteflow.OidcPresetProto\"\xea\x01\n\x10\x45xportRulesProto\x12\x33\n\x0e\x64\x65\x66\x61ult_format\x18\x01 \x01(\x0e\x32\x16.noteflow.ExportFormatH\x00\x88\x01\x01\x12\x1a\n\rinclude_audio\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x1f\n\x12include_timestamps\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x18\n\x0btemplate_id\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x11\n\x0f_default_formatB\x10\n\x0e_include_audioB\x15\n\x13_include_timestampsB\x0e\n\x0c_template_id\"\x88\x01\n\x11TriggerRulesProto\x12\x1f\n\x12\x61uto_start_enabled\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x1f\n\x17\x63\x61lendar_match_patterns\x18\x02 \x03(\t\x12\x1a\n\x12\x61pp_match_patterns\x18\x03 \x03(\tB\x15\n\x13_auto_start_enabled\"\xa5\x02\n\x16WorkspaceSettingsProto\x12\x35\n\x0c\x65xport_rules\x18\x01 \x01(\x0b\x32\x1a.noteflow.ExportRulesProtoH\x00\x88\x01\x01\x12\x37\n\rtrigger_rules\x18\x02 \x01(\x0b\x32\x1b.noteflow.TriggerRulesProtoH\x01\x88\x01\x01\x12\x18\n\x0brag_enabled\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12+\n\x1e\x64\x65\x66\x61ult_summarization_template\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x0f\n\r_export_rulesB\x10\n\x0e_trigger_rulesB\x0e\n\x0c_rag_enabledB!\n\x1f_default_summarization_template\"\xa3\x02\n\x14ProjectSettingsProto\x12\x35\n\x0c\x65xport_rules\x18\x01 \x01(\x0b\x32\x1a.noteflow.ExportRulesProtoH\x00\x88\x01\x01\x12\x37\n\rtrigger_rules\x18\x02 \x01(\x0b\x32\x1b.noteflow.TriggerRulesProtoH\x01\x88\x01\x01\x12\x18\n\x0brag_enabled\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12+\n\x1e\x64\x65\x66\x61ult_summarization_template\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x0f\n\r_export_rulesB\x10\n\x0e_trigger_rulesB\x0e\n\x0c_rag_enabledB!\n\x1f_default_summarization_template\"\xc3\x02\n\x0cProjectProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\x04slug\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x12\n\nis_default\x18\x06 \x01(\x08\x12\x13\n\x0bis_archived\x18\x07 \x01(\x08\x12\x35\n\x08settings\x18\x08 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\t \x01(\x03\x12\x12\n\nupdated_at\x18\n \x01(\x03\x12\x18\n\x0b\x61rchived_at\x18\x0b \x01(\x03H\x03\x88\x01\x01\x42\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settingsB\x0e\n\x0c_archived_at\"z\n\x16ProjectMembershipProto\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\x12\x11\n\tjoined_at\x18\x04 \x01(\x03\"\xc4\x01\n\x14\x43reateProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\x04slug\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x35\n\x08settings\x18\x05 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x02\x88\x01\x01\x42\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settings\"\'\n\x11GetProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"=\n\x17GetProjectBySlugRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04slug\x18\x02 \x01(\t\"d\n\x13ListProjectsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x18\n\x10include_archived\x18\x02 \x01(\x08\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\"U\n\x14ListProjectsResponse\x12(\n\x08projects\x18\x01 \x03(\x0b\x32\x16.noteflow.ProjectProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xd0\x01\n\x14UpdateProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04slug\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x08settings\x18\x05 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x03\x88\x01\x01\x42\x07\n\x05_nameB\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settings\"+\n\x15\x41rchiveProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"+\n\x15RestoreProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"*\n\x14\x44\x65leteProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteProjectResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"C\n\x17SetActiveProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x12\n\nproject_id\x18\x02 \x01(\t\"\x1a\n\x18SetActiveProjectResponse\"/\n\x17GetActiveProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"k\n\x18GetActiveProjectResponse\x12\x17\n\nproject_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\'\n\x07project\x18\x02 \x01(\x0b\x32\x16.noteflow.ProjectProtoB\r\n\x0b_project_id\"h\n\x17\x41\x64\x64ProjectMemberRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\"o\n\x1eUpdateProjectMemberRoleRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\"A\n\x1aRemoveProjectMemberRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\".\n\x1bRemoveProjectMemberResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"N\n\x19ListProjectMembersRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"d\n\x1aListProjectMembersResponse\x12\x31\n\x07members\x18\x01 \x03(\x0b\x32 .noteflow.ProjectMembershipProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x17\n\x15GetCurrentUserRequest\"\xbb\x01\n\x16GetCurrentUserResponse\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\x12\r\n\x05\x65mail\x18\x04 \x01(\t\x12\x18\n\x10is_authenticated\x18\x05 \x01(\x08\x12\x15\n\rauth_provider\x18\x06 \x01(\t\x12\x16\n\x0eworkspace_name\x18\x07 \x01(\t\x12\x0c\n\x04role\x18\x08 \x01(\t\"Z\n\x0eWorkspaceProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04slug\x18\x03 \x01(\t\x12\x12\n\nis_default\x18\x04 \x01(\x08\x12\x0c\n\x04role\x18\x05 \x01(\t\"6\n\x15ListWorkspacesRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\"[\n\x16ListWorkspacesResponse\x12,\n\nworkspaces\x18\x01 \x03(\x0b\x32\x18.noteflow.WorkspaceProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\".\n\x16SwitchWorkspaceRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"n\n\x17SwitchWorkspaceResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12+\n\tworkspace\x18\x02 \x01(\x0b\x32\x18.noteflow.WorkspaceProto\x12\x15\n\rerror_message\x18\x03 \x01(\t\"3\n\x1bGetWorkspaceSettingsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"j\n\x1eUpdateWorkspaceSettingsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x32\n\x08settings\x18\x02 \x01(\x0b\x32 .noteflow.WorkspaceSettingsProto\"\xfa\x01\n\tTaskProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nmeeting_id\x18\x02 \x01(\t\x12\x16\n\x0e\x61\x63tion_item_id\x18\x03 \x01(\x05\x12\x0c\n\x04text\x18\x04 \x01(\t\x12)\n\x06status\x18\x05 \x01(\x0e\x32\x19.noteflow.TaskStatusProto\x12\x1a\n\x12\x61ssignee_person_id\x18\x06 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x07 \x01(\x01\x12\x10\n\x08priority\x18\x08 \x01(\x05\x12\x14\n\x0c\x63ompleted_at\x18\t \x01(\x01\x12\x12\n\ncreated_at\x18\n \x01(\x01\x12\x12\n\nupdated_at\x18\x0b \x01(\x01\"\x80\x01\n\x14TaskWithMeetingProto\x12!\n\x04task\x18\x01 \x01(\x0b\x32\x13.noteflow.TaskProto\x12\x15\n\rmeeting_title\x18\x02 \x01(\t\x12\x1a\n\x12meeting_created_at\x18\x03 \x01(\x01\x12\x12\n\nproject_id\x18\x04 \x01(\t\"\xc3\x01\n\x10ListTasksRequest\x12+\n\x08statuses\x18\x01 \x03(\x0e\x32\x19.noteflow.TaskStatusProto\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\x17\n\nproject_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x05 \x03(\t\x12\x17\n\nmeeting_id\x18\x06 \x01(\tH\x01\x88\x01\x01\x42\r\n\x0b_project_idB\r\n\x0b_meeting_id\"W\n\x11ListTasksResponse\x12-\n\x05tasks\x18\x01 \x03(\x0b\x32\x1e.noteflow.TaskWithMeetingProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x9d\x01\n\x11UpdateTaskRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12)\n\x06status\x18\x03 \x01(\x0e\x32\x19.noteflow.TaskStatusProto\x12\x1a\n\x12\x61ssignee_person_id\x18\x04 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x05 \x01(\x01\x12\x10\n\x08priority\x18\x06 \x01(\x05\"7\n\x12UpdateTaskResponse\x12!\n\x04task\x18\x01 \x01(\x0b\x32\x13.noteflow.TaskProto\"\x80\x01\n\x1bGetAnalyticsOverviewRequest\x12\x12\n\nstart_time\x18\x01 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x01\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x04 \x03(\tB\r\n\x0b_project_id\"d\n\x16\x44\x61ilyMeetingStatsProto\x12\x0c\n\x04\x64\x61te\x18\x01 \x01(\t\x12\x10\n\x08meetings\x18\x02 \x01(\x05\x12\x16\n\x0etotal_duration\x18\x03 \x01(\x01\x12\x12\n\nword_count\x18\x04 \x01(\x05\"\xc3\x01\n\x1cGetAnalyticsOverviewResponse\x12/\n\x05\x64\x61ily\x18\x01 \x03(\x0b\x32 .noteflow.DailyMeetingStatsProto\x12\x16\n\x0etotal_meetings\x18\x02 \x01(\x05\x12\x16\n\x0etotal_duration\x18\x03 \x01(\x01\x12\x13\n\x0btotal_words\x18\x04 \x01(\x05\x12\x16\n\x0etotal_segments\x18\x05 \x01(\x05\x12\x15\n\rspeaker_count\x18\x06 \x01(\x05\"\x96\x01\n\x10SpeakerStatProto\x12\x12\n\nspeaker_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x12\n\ntotal_time\x18\x03 \x01(\x01\x12\x15\n\rsegment_count\x18\x04 \x01(\x05\x12\x15\n\rmeeting_count\x18\x05 \x01(\x05\x12\x16\n\x0e\x61vg_confidence\x18\x06 \x01(\x01\"|\n\x17ListSpeakerStatsRequest\x12\x12\n\nstart_time\x18\x01 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x01\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x04 \x03(\tB\r\n\x0b_project_id\"H\n\x18ListSpeakerStatsResponse\x12,\n\x08speakers\x18\x01 \x03(\x0b\x32\x1a.noteflow.SpeakerStatProto*\x8d\x01\n\nUpdateType\x12\x1b\n\x17UPDATE_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13UPDATE_TYPE_PARTIAL\x10\x01\x12\x15\n\x11UPDATE_TYPE_FINAL\x10\x02\x12\x19\n\x15UPDATE_TYPE_VAD_START\x10\x03\x12\x17\n\x13UPDATE_TYPE_VAD_END\x10\x04*\xb6\x01\n\x0cMeetingState\x12\x1d\n\x19MEETING_STATE_UNSPECIFIED\x10\x00\x12\x19\n\x15MEETING_STATE_CREATED\x10\x01\x12\x1b\n\x17MEETING_STATE_RECORDING\x10\x02\x12\x19\n\x15MEETING_STATE_STOPPED\x10\x03\x12\x1b\n\x17MEETING_STATE_COMPLETED\x10\x04\x12\x17\n\x13MEETING_STATE_ERROR\x10\x05*`\n\tSortOrder\x12\x1a\n\x16SORT_ORDER_UNSPECIFIED\x10\x00\x12\x1b\n\x17SORT_ORDER_CREATED_DESC\x10\x01\x12\x1a\n\x16SORT_ORDER_CREATED_ASC\x10\x02*^\n\x08Priority\x12\x18\n\x14PRIORITY_UNSPECIFIED\x10\x00\x12\x10\n\x0cPRIORITY_LOW\x10\x01\x12\x13\n\x0fPRIORITY_MEDIUM\x10\x02\x12\x11\n\rPRIORITY_HIGH\x10\x03*e\n\tAsrDevice\x12\x1a\n\x16\x41SR_DEVICE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x41SR_DEVICE_CPU\x10\x01\x12\x13\n\x0f\x41SR_DEVICE_CUDA\x10\x02\x12\x13\n\x0f\x41SR_DEVICE_ROCM\x10\x03*\x89\x01\n\x0e\x41srComputeType\x12 \n\x1c\x41SR_COMPUTE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SR_COMPUTE_TYPE_INT8\x10\x01\x12\x1c\n\x18\x41SR_COMPUTE_TYPE_FLOAT16\x10\x02\x12\x1c\n\x18\x41SR_COMPUTE_TYPE_FLOAT32\x10\x03*\xa4\x01\n\x0e\x41nnotationType\x12\x1f\n\x1b\x41NNOTATION_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x41NNOTATION_TYPE_ACTION_ITEM\x10\x01\x12\x1c\n\x18\x41NNOTATION_TYPE_DECISION\x10\x02\x12\x18\n\x14\x41NNOTATION_TYPE_NOTE\x10\x03\x12\x18\n\x14\x41NNOTATION_TYPE_RISK\x10\x04*x\n\x0c\x45xportFormat\x12\x1d\n\x19\x45XPORT_FORMAT_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x45XPORT_FORMAT_MARKDOWN\x10\x01\x12\x16\n\x12\x45XPORT_FORMAT_HTML\x10\x02\x12\x15\n\x11\x45XPORT_FORMAT_PDF\x10\x03*\xa1\x01\n\tJobStatus\x12\x1a\n\x16JOB_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11JOB_STATUS_QUEUED\x10\x01\x12\x16\n\x12JOB_STATUS_RUNNING\x10\x02\x12\x18\n\x14JOB_STATUS_COMPLETED\x10\x03\x12\x15\n\x11JOB_STATUS_FAILED\x10\x04\x12\x18\n\x14JOB_STATUS_CANCELLED\x10\x05*\xb8\x01\n\rSyncErrorCode\x12\x1f\n\x1bSYNC_ERROR_CODE_UNSPECIFIED\x10\x00\x12!\n\x1dSYNC_ERROR_CODE_AUTH_REQUIRED\x10\x01\x12\"\n\x1eSYNC_ERROR_CODE_PROVIDER_ERROR\x10\x02\x12\"\n\x1eSYNC_ERROR_CODE_INTERNAL_ERROR\x10\x03\x12\x1b\n\x17SYNC_ERROR_CODE_UNKNOWN\x10\x04*\xc9\x01\n\x14ProcessingStepStatus\x12\x1f\n\x1bPROCESSING_STEP_UNSPECIFIED\x10\x00\x12\x1b\n\x17PROCESSING_STEP_PENDING\x10\x01\x12\x1b\n\x17PROCESSING_STEP_RUNNING\x10\x02\x12\x1d\n\x19PROCESSING_STEP_COMPLETED\x10\x03\x12\x1a\n\x16PROCESSING_STEP_FAILED\x10\x04\x12\x1b\n\x17PROCESSING_STEP_SKIPPED\x10\x05*z\n\x10ProjectRoleProto\x12\x1c\n\x18PROJECT_ROLE_UNSPECIFIED\x10\x00\x12\x17\n\x13PROJECT_ROLE_VIEWER\x10\x01\x12\x17\n\x13PROJECT_ROLE_EDITOR\x10\x02\x12\x16\n\x12PROJECT_ROLE_ADMIN\x10\x03*u\n\x0fTaskStatusProto\x12\x1b\n\x17TASK_STATUS_UNSPECIFIED\x10\x00\x12\x14\n\x10TASK_STATUS_OPEN\x10\x01\x12\x14\n\x10TASK_STATUS_DONE\x10\x02\x12\x19\n\x15TASK_STATUS_DISMISSED\x10\x03\x32\xfc\x42\n\x0fNoteFlowService\x12K\n\x13StreamTranscription\x12\x14.noteflow.AudioChunk\x1a\x1a.noteflow.TranscriptUpdate(\x01\x30\x01\x12\x42\n\rCreateMeeting\x12\x1e.noteflow.CreateMeetingRequest\x1a\x11.noteflow.Meeting\x12>\n\x0bStopMeeting\x12\x1c.noteflow.StopMeetingRequest\x1a\x11.noteflow.Meeting\x12M\n\x0cListMeetings\x12\x1d.noteflow.ListMeetingsRequest\x1a\x1e.noteflow.ListMeetingsResponse\x12<\n\nGetMeeting\x12\x1b.noteflow.GetMeetingRequest\x1a\x11.noteflow.Meeting\x12P\n\rDeleteMeeting\x12\x1e.noteflow.DeleteMeetingRequest\x1a\x1f.noteflow.DeleteMeetingResponse\x12\x46\n\x0fGenerateSummary\x12 .noteflow.GenerateSummaryRequest\x1a\x11.noteflow.Summary\x12w\n\x1aListSummarizationTemplates\x12+.noteflow.ListSummarizationTemplatesRequest\x1a,.noteflow.ListSummarizationTemplatesResponse\x12q\n\x18GetSummarizationTemplate\x12).noteflow.GetSummarizationTemplateRequest\x1a*.noteflow.GetSummarizationTemplateResponse\x12|\n\x1b\x43reateSummarizationTemplate\x12,.noteflow.CreateSummarizationTemplateRequest\x1a/.noteflow.SummarizationTemplateMutationResponse\x12|\n\x1bUpdateSummarizationTemplate\x12,.noteflow.UpdateSummarizationTemplateRequest\x1a/.noteflow.SummarizationTemplateMutationResponse\x12s\n\x1c\x41rchiveSummarizationTemplate\x12-.noteflow.ArchiveSummarizationTemplateRequest\x1a$.noteflow.SummarizationTemplateProto\x12\x8c\x01\n!ListSummarizationTemplateVersions\x12\x32.noteflow.ListSummarizationTemplateVersionsRequest\x1a\x33.noteflow.ListSummarizationTemplateVersionsResponse\x12\x81\x01\n#RestoreSummarizationTemplateVersion\x12\x34.noteflow.RestoreSummarizationTemplateVersionRequest\x1a$.noteflow.SummarizationTemplateProto\x12\x45\n\rAddAnnotation\x12\x1e.noteflow.AddAnnotationRequest\x1a\x14.noteflow.Annotation\x12\x45\n\rGetAnnotation\x12\x1e.noteflow.GetAnnotationRequest\x1a\x14.noteflow.Annotation\x12V\n\x0fListAnnotations\x12 .noteflow.ListAnnotationsRequest\x1a!.noteflow.ListAnnotationsResponse\x12K\n\x10UpdateAnnotation\x12!.noteflow.UpdateAnnotationRequest\x1a\x14.noteflow.Annotation\x12Y\n\x10\x44\x65leteAnnotation\x12!.noteflow.DeleteAnnotationRequest\x1a\".noteflow.DeleteAnnotationResponse\x12Y\n\x10\x45xportTranscript\x12!.noteflow.ExportTranscriptRequest\x1a\".noteflow.ExportTranscriptResponse\x12q\n\x18RefineSpeakerDiarization\x12).noteflow.RefineSpeakerDiarizationRequest\x1a*.noteflow.RefineSpeakerDiarizationResponse\x12P\n\rRenameSpeaker\x12\x1e.noteflow.RenameSpeakerRequest\x1a\x1f.noteflow.RenameSpeakerResponse\x12\x63\n\x17GetDiarizationJobStatus\x12(.noteflow.GetDiarizationJobStatusRequest\x1a\x1e.noteflow.DiarizationJobStatus\x12\x65\n\x14\x43\x61ncelDiarizationJob\x12%.noteflow.CancelDiarizationJobRequest\x1a&.noteflow.CancelDiarizationJobResponse\x12q\n\x18GetActiveDiarizationJobs\x12).noteflow.GetActiveDiarizationJobsRequest\x1a*.noteflow.GetActiveDiarizationJobsResponse\x12\x42\n\rGetServerInfo\x12\x1b.noteflow.ServerInfoRequest\x1a\x14.noteflow.ServerInfo\x12\x62\n\x13GetAsrConfiguration\x12$.noteflow.GetAsrConfigurationRequest\x1a%.noteflow.GetAsrConfigurationResponse\x12k\n\x16UpdateAsrConfiguration\x12\'.noteflow.UpdateAsrConfigurationRequest\x1a(.noteflow.UpdateAsrConfigurationResponse\x12r\n\x1cGetAsrConfigurationJobStatus\x12-.noteflow.GetAsrConfigurationJobStatusRequest\x1a#.noteflow.AsrConfigurationJobStatus\x12t\n\x19GetStreamingConfiguration\x12*.noteflow.GetStreamingConfigurationRequest\x1a+.noteflow.GetStreamingConfigurationResponse\x12}\n\x1cUpdateStreamingConfiguration\x12-.noteflow.UpdateStreamingConfigurationRequest\x1a..noteflow.UpdateStreamingConfigurationResponse\x12V\n\x0f\x45xtractEntities\x12 .noteflow.ExtractEntitiesRequest\x1a!.noteflow.ExtractEntitiesResponse\x12M\n\x0cUpdateEntity\x12\x1d.noteflow.UpdateEntityRequest\x1a\x1e.noteflow.UpdateEntityResponse\x12M\n\x0c\x44\x65leteEntity\x12\x1d.noteflow.DeleteEntityRequest\x1a\x1e.noteflow.DeleteEntityResponse\x12_\n\x12ListCalendarEvents\x12#.noteflow.ListCalendarEventsRequest\x1a$.noteflow.ListCalendarEventsResponse\x12\x65\n\x14GetCalendarProviders\x12%.noteflow.GetCalendarProvidersRequest\x1a&.noteflow.GetCalendarProvidersResponse\x12P\n\rInitiateOAuth\x12\x1e.noteflow.InitiateOAuthRequest\x1a\x1f.noteflow.InitiateOAuthResponse\x12P\n\rCompleteOAuth\x12\x1e.noteflow.CompleteOAuthRequest\x1a\x1f.noteflow.CompleteOAuthResponse\x12q\n\x18GetOAuthConnectionStatus\x12).noteflow.GetOAuthConnectionStatusRequest\x1a*.noteflow.GetOAuthConnectionStatusResponse\x12V\n\x0f\x44isconnectOAuth\x12 .noteflow.DisconnectOAuthRequest\x1a!.noteflow.DisconnectOAuthResponse\x12\x65\n\x14GetOAuthClientConfig\x12%.noteflow.GetOAuthClientConfigRequest\x1a&.noteflow.GetOAuthClientConfigResponse\x12\x65\n\x14SetOAuthClientConfig\x12%.noteflow.SetOAuthClientConfigRequest\x1a&.noteflow.SetOAuthClientConfigResponse\x12Q\n\x0fRegisterWebhook\x12 .noteflow.RegisterWebhookRequest\x1a\x1c.noteflow.WebhookConfigProto\x12M\n\x0cListWebhooks\x12\x1d.noteflow.ListWebhooksRequest\x1a\x1e.noteflow.ListWebhooksResponse\x12M\n\rUpdateWebhook\x12\x1e.noteflow.UpdateWebhookRequest\x1a\x1c.noteflow.WebhookConfigProto\x12P\n\rDeleteWebhook\x12\x1e.noteflow.DeleteWebhookRequest\x1a\x1f.noteflow.DeleteWebhookResponse\x12\x65\n\x14GetWebhookDeliveries\x12%.noteflow.GetWebhookDeliveriesRequest\x1a&.noteflow.GetWebhookDeliveriesResponse\x12\\\n\x11GrantCloudConsent\x12\".noteflow.GrantCloudConsentRequest\x1a#.noteflow.GrantCloudConsentResponse\x12_\n\x12RevokeCloudConsent\x12#.noteflow.RevokeCloudConsentRequest\x1a$.noteflow.RevokeCloudConsentResponse\x12h\n\x15GetCloudConsentStatus\x12&.noteflow.GetCloudConsentStatusRequest\x1a\'.noteflow.GetCloudConsentStatusResponse\x12\x62\n\x13SetHuggingFaceToken\x12$.noteflow.SetHuggingFaceTokenRequest\x1a%.noteflow.SetHuggingFaceTokenResponse\x12t\n\x19GetHuggingFaceTokenStatus\x12*.noteflow.GetHuggingFaceTokenStatusRequest\x1a+.noteflow.GetHuggingFaceTokenStatusResponse\x12k\n\x16\x44\x65leteHuggingFaceToken\x12\'.noteflow.DeleteHuggingFaceTokenRequest\x1a(.noteflow.DeleteHuggingFaceTokenResponse\x12q\n\x18ValidateHuggingFaceToken\x12).noteflow.ValidateHuggingFaceTokenRequest\x1a*.noteflow.ValidateHuggingFaceTokenResponse\x12S\n\x0eGetPreferences\x12\x1f.noteflow.GetPreferencesRequest\x1a .noteflow.GetPreferencesResponse\x12S\n\x0eSetPreferences\x12\x1f.noteflow.SetPreferencesRequest\x1a .noteflow.SetPreferencesResponse\x12\x65\n\x14StartIntegrationSync\x12%.noteflow.StartIntegrationSyncRequest\x1a&.noteflow.StartIntegrationSyncResponse\x12P\n\rGetSyncStatus\x12\x1e.noteflow.GetSyncStatusRequest\x1a\x1f.noteflow.GetSyncStatusResponse\x12V\n\x0fListSyncHistory\x12 .noteflow.ListSyncHistoryRequest\x1a!.noteflow.ListSyncHistoryResponse\x12\x62\n\x13GetUserIntegrations\x12$.noteflow.GetUserIntegrationsRequest\x1a%.noteflow.GetUserIntegrationsResponse\x12P\n\rGetRecentLogs\x12\x1e.noteflow.GetRecentLogsRequest\x1a\x1f.noteflow.GetRecentLogsResponse\x12h\n\x15GetPerformanceMetrics\x12&.noteflow.GetPerformanceMetricsRequest\x1a\'.noteflow.GetPerformanceMetricsResponse\x12Z\n\x14RegisterOidcProvider\x12%.noteflow.RegisterOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12\\\n\x11ListOidcProviders\x12\".noteflow.ListOidcProvidersRequest\x1a#.noteflow.ListOidcProvidersResponse\x12P\n\x0fGetOidcProvider\x12 .noteflow.GetOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12V\n\x12UpdateOidcProvider\x12#.noteflow.UpdateOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12_\n\x12\x44\x65leteOidcProvider\x12#.noteflow.DeleteOidcProviderRequest\x1a$.noteflow.DeleteOidcProviderResponse\x12\x65\n\x14RefreshOidcDiscovery\x12%.noteflow.RefreshOidcDiscoveryRequest\x1a&.noteflow.RefreshOidcDiscoveryResponse\x12V\n\x0fListOidcPresets\x12 .noteflow.ListOidcPresetsRequest\x1a!.noteflow.ListOidcPresetsResponse\x12G\n\rCreateProject\x12\x1e.noteflow.CreateProjectRequest\x1a\x16.noteflow.ProjectProto\x12\x41\n\nGetProject\x12\x1b.noteflow.GetProjectRequest\x1a\x16.noteflow.ProjectProto\x12M\n\x10GetProjectBySlug\x12!.noteflow.GetProjectBySlugRequest\x1a\x16.noteflow.ProjectProto\x12M\n\x0cListProjects\x12\x1d.noteflow.ListProjectsRequest\x1a\x1e.noteflow.ListProjectsResponse\x12G\n\rUpdateProject\x12\x1e.noteflow.UpdateProjectRequest\x1a\x16.noteflow.ProjectProto\x12I\n\x0e\x41rchiveProject\x12\x1f.noteflow.ArchiveProjectRequest\x1a\x16.noteflow.ProjectProto\x12I\n\x0eRestoreProject\x12\x1f.noteflow.RestoreProjectRequest\x1a\x16.noteflow.ProjectProto\x12P\n\rDeleteProject\x12\x1e.noteflow.DeleteProjectRequest\x1a\x1f.noteflow.DeleteProjectResponse\x12Y\n\x10SetActiveProject\x12!.noteflow.SetActiveProjectRequest\x1a\".noteflow.SetActiveProjectResponse\x12Y\n\x10GetActiveProject\x12!.noteflow.GetActiveProjectRequest\x1a\".noteflow.GetActiveProjectResponse\x12\x44\n\tListTasks\x12\x1a.noteflow.ListTasksRequest\x1a\x1b.noteflow.ListTasksResponse\x12G\n\nUpdateTask\x12\x1b.noteflow.UpdateTaskRequest\x1a\x1c.noteflow.UpdateTaskResponse\x12\x65\n\x14GetAnalyticsOverview\x12%.noteflow.GetAnalyticsOverviewRequest\x1a&.noteflow.GetAnalyticsOverviewResponse\x12Y\n\x10ListSpeakerStats\x12!.noteflow.ListSpeakerStatsRequest\x1a\".noteflow.ListSpeakerStatsResponse\x12W\n\x10\x41\x64\x64ProjectMember\x12!.noteflow.AddProjectMemberRequest\x1a .noteflow.ProjectMembershipProto\x12\x65\n\x17UpdateProjectMemberRole\x12(.noteflow.UpdateProjectMemberRoleRequest\x1a .noteflow.ProjectMembershipProto\x12\x62\n\x13RemoveProjectMember\x12$.noteflow.RemoveProjectMemberRequest\x1a%.noteflow.RemoveProjectMemberResponse\x12_\n\x12ListProjectMembers\x12#.noteflow.ListProjectMembersRequest\x1a$.noteflow.ListProjectMembersResponse\x12S\n\x0eGetCurrentUser\x12\x1f.noteflow.GetCurrentUserRequest\x1a .noteflow.GetCurrentUserResponse\x12S\n\x0eListWorkspaces\x12\x1f.noteflow.ListWorkspacesRequest\x1a .noteflow.ListWorkspacesResponse\x12V\n\x0fSwitchWorkspace\x12 .noteflow.SwitchWorkspaceRequest\x1a!.noteflow.SwitchWorkspaceResponse\x12_\n\x14GetWorkspaceSettings\x12%.noteflow.GetWorkspaceSettingsRequest\x1a .noteflow.WorkspaceSettingsProto\x12\x65\n\x17UpdateWorkspaceSettings\x12(.noteflow.UpdateWorkspaceSettingsRequest\x1a .noteflow.WorkspaceSettingsProtob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0enoteflow.proto\x12\x08noteflow\"\x86\x01\n\nAudioChunk\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x12\n\naudio_data\x18\x02 \x01(\x0c\x12\x11\n\ttimestamp\x18\x03 \x01(\x01\x12\x13\n\x0bsample_rate\x18\x04 \x01(\x05\x12\x10\n\x08\x63hannels\x18\x05 \x01(\x05\x12\x16\n\x0e\x63hunk_sequence\x18\x06 \x01(\x03\"`\n\x0e\x43ongestionInfo\x12\x1b\n\x13processing_delay_ms\x18\x01 \x01(\x05\x12\x13\n\x0bqueue_depth\x18\x02 \x01(\x05\x12\x1c\n\x14throttle_recommended\x18\x03 \x01(\x08\"\x98\x02\n\x10TranscriptUpdate\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12)\n\x0bupdate_type\x18\x02 \x01(\x0e\x32\x14.noteflow.UpdateType\x12\x14\n\x0cpartial_text\x18\x03 \x01(\t\x12\'\n\x07segment\x18\x04 \x01(\x0b\x32\x16.noteflow.FinalSegment\x12\x18\n\x10server_timestamp\x18\x05 \x01(\x01\x12\x19\n\x0c\x61\x63k_sequence\x18\x06 \x01(\x03H\x00\x88\x01\x01\x12\x31\n\ncongestion\x18\n \x01(\x0b\x32\x18.noteflow.CongestionInfoH\x01\x88\x01\x01\x42\x0f\n\r_ack_sequenceB\r\n\x0b_congestion\"\x87\x02\n\x0c\x46inalSegment\x12\x12\n\nsegment_id\x18\x01 \x01(\x05\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x01\x12#\n\x05words\x18\x05 \x03(\x0b\x32\x14.noteflow.WordTiming\x12\x10\n\x08language\x18\x06 \x01(\t\x12\x1b\n\x13language_confidence\x18\x07 \x01(\x02\x12\x13\n\x0b\x61vg_logprob\x18\x08 \x01(\x02\x12\x16\n\x0eno_speech_prob\x18\t \x01(\x02\x12\x12\n\nspeaker_id\x18\n \x01(\t\x12\x1a\n\x12speaker_confidence\x18\x0b \x01(\x02\"U\n\nWordTiming\x12\x0c\n\x04word\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x01\x12\x13\n\x0bprobability\x18\x04 \x01(\x02\"\xb0\x03\n\x07Meeting\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12%\n\x05state\x18\x03 \x01(\x0e\x32\x16.noteflow.MeetingState\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\x12\n\nstarted_at\x18\x05 \x01(\x01\x12\x10\n\x08\x65nded_at\x18\x06 \x01(\x01\x12\x18\n\x10\x64uration_seconds\x18\x07 \x01(\x01\x12(\n\x08segments\x18\x08 \x03(\x0b\x32\x16.noteflow.FinalSegment\x12\"\n\x07summary\x18\t \x01(\x0b\x32\x11.noteflow.Summary\x12\x31\n\x08metadata\x18\n \x03(\x0b\x32\x1f.noteflow.Meeting.MetadataEntry\x12\x17\n\nproject_id\x18\x0b \x01(\tH\x00\x88\x01\x01\x12\x35\n\x11processing_status\x18\x0c \x01(\x0b\x32\x1a.noteflow.ProcessingStatus\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_project_id\"\xbe\x01\n\x14\x43reateMeetingRequest\x12\r\n\x05title\x18\x01 \x01(\t\x12>\n\x08metadata\x18\x02 \x03(\x0b\x32,.noteflow.CreateMeetingRequest.MetadataEntry\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_project_id\"(\n\x12StopMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\"\xdc\x01\n\x13ListMeetingsRequest\x12&\n\x06states\x18\x01 \x03(\x0e\x32\x16.noteflow.MeetingState\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\'\n\nsort_order\x18\x04 \x01(\x0e\x32\x13.noteflow.SortOrder\x12\x17\n\nproject_id\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x06 \x03(\t\x12\x18\n\x10include_segments\x18\x07 \x01(\x08\x42\r\n\x0b_project_id\"P\n\x14ListMeetingsResponse\x12#\n\x08meetings\x18\x01 \x03(\x0b\x32\x11.noteflow.Meeting\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"Z\n\x11GetMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x18\n\x10include_segments\x18\x02 \x01(\x08\x12\x17\n\x0finclude_summary\x18\x03 \x01(\x08\"*\n\x14\x44\x65leteMeetingRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteMeetingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xb9\x01\n\x07Summary\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x19\n\x11\x65xecutive_summary\x18\x02 \x01(\t\x12&\n\nkey_points\x18\x03 \x03(\x0b\x32\x12.noteflow.KeyPoint\x12*\n\x0c\x61\x63tion_items\x18\x04 \x03(\x0b\x32\x14.noteflow.ActionItem\x12\x14\n\x0cgenerated_at\x18\x05 \x01(\x01\x12\x15\n\rmodel_version\x18\x06 \x01(\t\"S\n\x08KeyPoint\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x13\n\x0bsegment_ids\x18\x02 \x03(\x05\x12\x12\n\nstart_time\x18\x03 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x01\"y\n\nActionItem\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x10\n\x08\x61ssignee\x18\x02 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x03 \x01(\x01\x12$\n\x08priority\x18\x04 \x01(\x0e\x32\x12.noteflow.Priority\x12\x13\n\x0bsegment_ids\x18\x05 \x03(\x05\"\\\n\x14SummarizationOptions\x12\x0c\n\x04tone\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\x11\n\tverbosity\x18\x03 \x01(\t\x12\x13\n\x0btemplate_id\x18\x04 \x01(\t\"w\n\x16GenerateSummaryRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x18\n\x10\x66orce_regenerate\x18\x02 \x01(\x08\x12/\n\x07options\x18\x03 \x01(\x0b\x32\x1e.noteflow.SummarizationOptions\"\xe4\x02\n\x1aSummarizationTemplateProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x19\n\x0cworkspace_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x11\n\tis_system\x18\x05 \x01(\x08\x12\x13\n\x0bis_archived\x18\x06 \x01(\x08\x12\x1f\n\x12\x63urrent_version_id\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x08 \x01(\x03\x12\x12\n\nupdated_at\x18\t \x01(\x03\x12\x17\n\ncreated_by\x18\n \x01(\tH\x03\x88\x01\x01\x12\x17\n\nupdated_by\x18\x0b \x01(\tH\x04\x88\x01\x01\x42\x0f\n\r_workspace_idB\x0e\n\x0c_descriptionB\x15\n\x13_current_version_idB\r\n\x0b_created_byB\r\n\x0b_updated_by\"\xd3\x01\n!SummarizationTemplateVersionProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0btemplate_id\x18\x02 \x01(\t\x12\x16\n\x0eversion_number\x18\x03 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x12\n\ncreated_at\x18\x06 \x01(\x03\x12\x17\n\ncreated_by\x18\x07 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_change_noteB\r\n\x0b_created_by\"\x8a\x01\n!ListSummarizationTemplatesRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x16\n\x0einclude_system\x18\x02 \x01(\x08\x12\x18\n\x10include_archived\x18\x03 \x01(\x08\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\"r\n\"ListSummarizationTemplatesResponse\x12\x37\n\ttemplates\x18\x01 \x03(\x0b\x32$.noteflow.SummarizationTemplateProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"W\n\x1fGetSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x1f\n\x17include_current_version\x18\x02 \x01(\x08\"\xb9\x01\n GetSummarizationTemplateResponse\x12\x36\n\x08template\x18\x01 \x01(\x0b\x32$.noteflow.SummarizationTemplateProto\x12I\n\x0f\x63urrent_version\x18\x02 \x01(\x0b\x32+.noteflow.SummarizationTemplateVersionProtoH\x00\x88\x01\x01\x42\x12\n\x10_current_version\"\xae\x01\n%SummarizationTemplateMutationResponse\x12\x36\n\x08template\x18\x01 \x01(\x0b\x32$.noteflow.SummarizationTemplateProto\x12\x41\n\x07version\x18\x02 \x01(\x0b\x32+.noteflow.SummarizationTemplateVersionProtoH\x00\x88\x01\x01\x42\n\n\x08_version\"\xad\x01\n\"CreateSummarizationTemplateRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_descriptionB\x0e\n\x0c_change_note\"\xcb\x01\n\"UpdateSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x14\n\x07\x63ontent\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x18\n\x0b\x63hange_note\x18\x05 \x01(\tH\x03\x88\x01\x01\x42\x07\n\x05_nameB\x0e\n\x0c_descriptionB\n\n\x08_contentB\x0e\n\x0c_change_note\":\n#ArchiveSummarizationTemplateRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\"^\n(ListSummarizationTemplateVersionsRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\x7f\n)ListSummarizationTemplateVersionsResponse\x12=\n\x08versions\x18\x01 \x03(\x0b\x32+.noteflow.SummarizationTemplateVersionProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"U\n*RestoreSummarizationTemplateVersionRequest\x12\x13\n\x0btemplate_id\x18\x01 \x01(\t\x12\x12\n\nversion_id\x18\x02 \x01(\t\"\x13\n\x11ServerInfoRequest\"\x83\x04\n\nServerInfo\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x11\n\tasr_model\x18\x02 \x01(\t\x12\x11\n\tasr_ready\x18\x03 \x01(\x08\x12\x1e\n\x16supported_sample_rates\x18\x04 \x03(\x05\x12\x16\n\x0emax_chunk_size\x18\x05 \x01(\x05\x12\x16\n\x0euptime_seconds\x18\x06 \x01(\x01\x12\x17\n\x0f\x61\x63tive_meetings\x18\x07 \x01(\x05\x12\x1b\n\x13\x64iarization_enabled\x18\x08 \x01(\x08\x12\x19\n\x11\x64iarization_ready\x18\t \x01(\x08\x12\x15\n\rstate_version\x18\n \x01(\x03\x12#\n\x16system_ram_total_bytes\x18\x0b \x01(\x03H\x00\x88\x01\x01\x12\'\n\x1asystem_ram_available_bytes\x18\x0c \x01(\x03H\x01\x88\x01\x01\x12!\n\x14gpu_vram_total_bytes\x18\r \x01(\x03H\x02\x88\x01\x01\x12%\n\x18gpu_vram_available_bytes\x18\x0e \x01(\x03H\x03\x88\x01\x01\x42\x19\n\x17_system_ram_total_bytesB\x1d\n\x1b_system_ram_available_bytesB\x17\n\x15_gpu_vram_total_bytesB\x1b\n\x19_gpu_vram_available_bytes\"\xac\x02\n\x10\x41srConfiguration\x12\x12\n\nmodel_size\x18\x01 \x01(\t\x12#\n\x06\x64\x65vice\x18\x02 \x01(\x0e\x32\x13.noteflow.AsrDevice\x12.\n\x0c\x63ompute_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AsrComputeType\x12\x10\n\x08is_ready\x18\x04 \x01(\x08\x12\x16\n\x0e\x63uda_available\x18\x05 \x01(\x08\x12\x1d\n\x15\x61vailable_model_sizes\x18\x06 \x03(\t\x12\x39\n\x17\x61vailable_compute_types\x18\x07 \x03(\x0e\x32\x18.noteflow.AsrComputeType\x12\x16\n\x0erocm_available\x18\x08 \x01(\x08\x12\x13\n\x0bgpu_backend\x18\t \x01(\t\"\x1c\n\x1aGetAsrConfigurationRequest\"P\n\x1bGetAsrConfigurationResponse\x12\x31\n\rconfiguration\x18\x01 \x01(\x0b\x32\x1a.noteflow.AsrConfiguration\"\xc2\x01\n\x1dUpdateAsrConfigurationRequest\x12\x17\n\nmodel_size\x18\x01 \x01(\tH\x00\x88\x01\x01\x12(\n\x06\x64\x65vice\x18\x02 \x01(\x0e\x32\x13.noteflow.AsrDeviceH\x01\x88\x01\x01\x12\x33\n\x0c\x63ompute_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AsrComputeTypeH\x02\x88\x01\x01\x42\r\n\x0b_model_sizeB\t\n\x07_deviceB\x0f\n\r_compute_type\"~\n\x1eUpdateAsrConfigurationResponse\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x04 \x01(\x08\"5\n#GetAsrConfigurationJobStatusRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"\xe2\x01\n\x19\x41srConfigurationJobStatus\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x18\n\x10progress_percent\x18\x03 \x01(\x02\x12\r\n\x05phase\x18\x04 \x01(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12:\n\x11new_configuration\x18\x06 \x01(\x0b\x32\x1a.noteflow.AsrConfigurationH\x00\x88\x01\x01\x42\x14\n\x12_new_configuration\"\xe9\x01\n\x16StreamingConfiguration\x12\x1f\n\x17partial_cadence_seconds\x18\x01 \x01(\x02\x12!\n\x19min_partial_audio_seconds\x18\x02 \x01(\x02\x12$\n\x1cmax_segment_duration_seconds\x18\x03 \x01(\x02\x12#\n\x1bmin_speech_duration_seconds\x18\x04 \x01(\x02\x12 \n\x18trailing_silence_seconds\x18\x05 \x01(\x02\x12\x1e\n\x16leading_buffer_seconds\x18\x06 \x01(\x02\"\"\n GetStreamingConfigurationRequest\"\\\n!GetStreamingConfigurationResponse\x12\x37\n\rconfiguration\x18\x01 \x01(\x0b\x32 .noteflow.StreamingConfiguration\"\xc7\x03\n#UpdateStreamingConfigurationRequest\x12$\n\x17partial_cadence_seconds\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12&\n\x19min_partial_audio_seconds\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12)\n\x1cmax_segment_duration_seconds\x18\x03 \x01(\x02H\x02\x88\x01\x01\x12(\n\x1bmin_speech_duration_seconds\x18\x04 \x01(\x02H\x03\x88\x01\x01\x12%\n\x18trailing_silence_seconds\x18\x05 \x01(\x02H\x04\x88\x01\x01\x12#\n\x16leading_buffer_seconds\x18\x06 \x01(\x02H\x05\x88\x01\x01\x42\x1a\n\x18_partial_cadence_secondsB\x1c\n\x1a_min_partial_audio_secondsB\x1f\n\x1d_max_segment_duration_secondsB\x1e\n\x1c_min_speech_duration_secondsB\x1b\n\x19_trailing_silence_secondsB\x19\n\x17_leading_buffer_seconds\"_\n$UpdateStreamingConfigurationResponse\x12\x37\n\rconfiguration\x18\x01 \x01(\x0b\x32 .noteflow.StreamingConfiguration\"\xbc\x01\n\nAnnotation\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nmeeting_id\x18\x02 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x03 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nstart_time\x18\x05 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x06 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x07 \x03(\x05\x12\x12\n\ncreated_at\x18\x08 \x01(\x01\"\xa6\x01\n\x14\x41\x64\x64\x41nnotationRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x02 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x12\n\nstart_time\x18\x04 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x05 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x06 \x03(\x05\"-\n\x14GetAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\"R\n\x16ListAnnotationsRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x01\"D\n\x17ListAnnotationsResponse\x12)\n\x0b\x61nnotations\x18\x01 \x03(\x0b\x32\x14.noteflow.Annotation\"\xac\x01\n\x17UpdateAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\x12\x31\n\x0f\x61nnotation_type\x18\x02 \x01(\x0e\x32\x18.noteflow.AnnotationType\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x12\n\nstart_time\x18\x04 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x05 \x01(\x01\x12\x13\n\x0bsegment_ids\x18\x06 \x03(\x05\"0\n\x17\x44\x65leteAnnotationRequest\x12\x15\n\rannotation_id\x18\x01 \x01(\t\"+\n\x18\x44\x65leteAnnotationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\x86\x01\n\x13ProcessingStepState\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.noteflow.ProcessingStepStatus\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x12\n\nstarted_at\x18\x03 \x01(\x01\x12\x14\n\x0c\x63ompleted_at\x18\x04 \x01(\x01\"\xa7\x01\n\x10ProcessingStatus\x12.\n\x07summary\x18\x01 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\x12/\n\x08\x65ntities\x18\x02 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\x12\x32\n\x0b\x64iarization\x18\x03 \x01(\x0b\x32\x1d.noteflow.ProcessingStepState\"U\n\x17\x45xportTranscriptRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12&\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x16.noteflow.ExportFormat\"X\n\x18\x45xportTranscriptResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x13\n\x0b\x66ormat_name\x18\x02 \x01(\t\x12\x16\n\x0e\x66ile_extension\x18\x03 \x01(\t\"K\n\x1fRefineSpeakerDiarizationRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x14\n\x0cnum_speakers\x18\x02 \x01(\x05\"\x9d\x01\n RefineSpeakerDiarizationResponse\x12\x18\n\x10segments_updated\x18\x01 \x01(\x05\x12\x13\n\x0bspeaker_ids\x18\x02 \x03(\t\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x0e\n\x06job_id\x18\x04 \x01(\t\x12#\n\x06status\x18\x05 \x01(\x0e\x32\x13.noteflow.JobStatus\"\\\n\x14RenameSpeakerRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x16\n\x0eold_speaker_id\x18\x02 \x01(\t\x12\x18\n\x10new_speaker_name\x18\x03 \x01(\t\"B\n\x15RenameSpeakerResponse\x12\x18\n\x10segments_updated\x18\x01 \x01(\x05\x12\x0f\n\x07success\x18\x02 \x01(\x08\"0\n\x1eGetDiarizationJobStatusRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"\xab\x01\n\x14\x44iarizationJobStatus\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12#\n\x06status\x18\x02 \x01(\x0e\x32\x13.noteflow.JobStatus\x12\x18\n\x10segments_updated\x18\x03 \x01(\x05\x12\x13\n\x0bspeaker_ids\x18\x04 \x03(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x18\n\x10progress_percent\x18\x06 \x01(\x02\"-\n\x1b\x43\x61ncelDiarizationJobRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\"k\n\x1c\x43\x61ncelDiarizationJobResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.noteflow.JobStatus\"!\n\x1fGetActiveDiarizationJobsRequest\"P\n GetActiveDiarizationJobsResponse\x12,\n\x04jobs\x18\x01 \x03(\x0b\x32\x1e.noteflow.DiarizationJobStatus\"C\n\x16\x45xtractEntitiesRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x15\n\rforce_refresh\x18\x02 \x01(\x08\"y\n\x0f\x45xtractedEntity\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x03 \x01(\t\x12\x13\n\x0bsegment_ids\x18\x04 \x03(\x05\x12\x12\n\nconfidence\x18\x05 \x01(\x02\x12\x11\n\tis_pinned\x18\x06 \x01(\x08\"k\n\x17\x45xtractEntitiesResponse\x12+\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x19.noteflow.ExtractedEntity\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x61\x63hed\x18\x03 \x01(\x08\"\\\n\x13UpdateEntityRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x11\n\tentity_id\x18\x02 \x01(\t\x12\x0c\n\x04text\x18\x03 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x04 \x01(\t\"A\n\x14UpdateEntityResponse\x12)\n\x06\x65ntity\x18\x01 \x01(\x0b\x32\x19.noteflow.ExtractedEntity\"<\n\x13\x44\x65leteEntityRequest\x12\x12\n\nmeeting_id\x18\x01 \x01(\t\x12\x11\n\tentity_id\x18\x02 \x01(\t\"\'\n\x14\x44\x65leteEntityResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xc7\x01\n\rCalendarEvent\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x04 \x01(\x03\x12\x11\n\tattendees\x18\x05 \x03(\t\x12\x10\n\x08location\x18\x06 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x13\n\x0bmeeting_url\x18\x08 \x01(\t\x12\x14\n\x0cis_recurring\x18\t \x01(\x08\x12\x10\n\x08provider\x18\n \x01(\t\"Q\n\x19ListCalendarEventsRequest\x12\x13\n\x0bhours_ahead\x18\x01 \x01(\x05\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x10\n\x08provider\x18\x03 \x01(\t\"Z\n\x1aListCalendarEventsResponse\x12\'\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x17.noteflow.CalendarEvent\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x1d\n\x1bGetCalendarProvidersRequest\"P\n\x10\x43\x61lendarProvider\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x18\n\x10is_authenticated\x18\x02 \x01(\x08\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\"M\n\x1cGetCalendarProvidersResponse\x12-\n\tproviders\x18\x01 \x03(\x0b\x32\x1a.noteflow.CalendarProvider\"X\n\x14InitiateOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x14\n\x0credirect_uri\x18\x02 \x01(\t\x12\x18\n\x10integration_type\x18\x03 \x01(\t\"8\n\x15InitiateOAuthResponse\x12\x10\n\x08\x61uth_url\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"E\n\x14\x43ompleteOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\"o\n\x15\x43ompleteOAuthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x16\n\x0eprovider_email\x18\x03 \x01(\t\x12\x16\n\x0eintegration_id\x18\x04 \x01(\t\"\x87\x01\n\x0fOAuthConnection\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x18\n\x10integration_type\x18\x06 \x01(\t\"M\n\x1fGetOAuthConnectionStatusRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\"Q\n GetOAuthConnectionStatusResponse\x12-\n\nconnection\x18\x01 \x01(\x0b\x32\x19.noteflow.OAuthConnection\"D\n\x16\x44isconnectOAuthRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\"A\n\x17\x44isconnectOAuthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\"\xaf\x01\n\x11OAuthClientConfig\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12\x1a\n\rclient_secret\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0credirect_uri\x18\x03 \x01(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\x12\x18\n\x10override_enabled\x18\x05 \x01(\x08\x12\x19\n\x11has_client_secret\x18\x06 \x01(\x08\x42\x10\n\x0e_client_secret\"_\n\x1bGetOAuthClientConfigRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x03 \x01(\t\"K\n\x1cGetOAuthClientConfigResponse\x12+\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1b.noteflow.OAuthClientConfig\"\x8c\x01\n\x1bSetOAuthClientConfigRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x18\n\x10integration_type\x18\x02 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x03 \x01(\t\x12+\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x1b.noteflow.OAuthClientConfig\"/\n\x1cSetOAuthClientConfigResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\x92\x01\n\x16RegisterWebhookRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06secret\x18\x05 \x01(\t\x12\x12\n\ntimeout_ms\x18\x06 \x01(\x05\x12\x13\n\x0bmax_retries\x18\x07 \x01(\x05\"\xc3\x01\n\x12WebhookConfigProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0b\n\x03url\x18\x04 \x01(\t\x12\x0e\n\x06\x65vents\x18\x05 \x03(\t\x12\x0f\n\x07\x65nabled\x18\x06 \x01(\x08\x12\x12\n\ntimeout_ms\x18\x07 \x01(\x05\x12\x13\n\x0bmax_retries\x18\x08 \x01(\x05\x12\x12\n\ncreated_at\x18\t \x01(\x03\x12\x12\n\nupdated_at\x18\n \x01(\x03\"+\n\x13ListWebhooksRequest\x12\x14\n\x0c\x65nabled_only\x18\x01 \x01(\x08\"[\n\x14ListWebhooksResponse\x12.\n\x08webhooks\x18\x01 \x03(\x0b\x32\x1c.noteflow.WebhookConfigProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x84\x02\n\x14UpdateWebhookRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\x12\x10\n\x03url\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06\x65vents\x18\x03 \x03(\t\x12\x11\n\x04name\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06secret\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x14\n\x07\x65nabled\x18\x06 \x01(\x08H\x03\x88\x01\x01\x12\x17\n\ntimeout_ms\x18\x07 \x01(\x05H\x04\x88\x01\x01\x12\x18\n\x0bmax_retries\x18\x08 \x01(\x05H\x05\x88\x01\x01\x42\x06\n\x04_urlB\x07\n\x05_nameB\t\n\x07_secretB\n\n\x08_enabledB\r\n\x0b_timeout_msB\x0e\n\x0c_max_retries\"*\n\x14\x44\x65leteWebhookRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteWebhookResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"\xcb\x01\n\x14WebhookDeliveryProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nwebhook_id\x18\x02 \x01(\t\x12\x12\n\nevent_type\x18\x03 \x01(\t\x12\x13\n\x0bstatus_code\x18\x04 \x01(\x05\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x15\n\rattempt_count\x18\x06 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x07 \x01(\x05\x12\x14\n\x0c\x64\x65livered_at\x18\x08 \x01(\x03\x12\x11\n\tsucceeded\x18\t \x01(\x08\"@\n\x1bGetWebhookDeliveriesRequest\x12\x12\n\nwebhook_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\"g\n\x1cGetWebhookDeliveriesResponse\x12\x32\n\ndeliveries\x18\x01 \x03(\x0b\x32\x1e.noteflow.WebhookDeliveryProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x1a\n\x18GrantCloudConsentRequest\"\x1b\n\x19GrantCloudConsentResponse\"\x1b\n\x19RevokeCloudConsentRequest\"\x1c\n\x1aRevokeCloudConsentResponse\"\x1e\n\x1cGetCloudConsentStatusRequest\"8\n\x1dGetCloudConsentStatusResponse\x12\x17\n\x0f\x63onsent_granted\x18\x01 \x01(\x08\"=\n\x1aSetHuggingFaceTokenRequest\x12\r\n\x05token\x18\x01 \x01(\t\x12\x10\n\x08validate\x18\x02 \x01(\x08\"x\n\x1bSetHuggingFaceTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x12\n\x05valid\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x18\n\x10validation_error\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\tB\x08\n\x06_valid\"\"\n GetHuggingFaceTokenStatusRequest\"x\n!GetHuggingFaceTokenStatusResponse\x12\x15\n\ris_configured\x18\x01 \x01(\x08\x12\x14\n\x0cis_validated\x18\x02 \x01(\x08\x12\x10\n\x08username\x18\x03 \x01(\t\x12\x14\n\x0cvalidated_at\x18\x04 \x01(\x01\"\x1f\n\x1d\x44\x65leteHuggingFaceTokenRequest\"1\n\x1e\x44\x65leteHuggingFaceTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"!\n\x1fValidateHuggingFaceTokenRequest\"Z\n ValidateHuggingFaceTokenResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x15\n\rerror_message\x18\x03 \x01(\t\"%\n\x15GetPreferencesRequest\x12\x0c\n\x04keys\x18\x01 \x03(\t\"\xb6\x01\n\x16GetPreferencesResponse\x12\x46\n\x0bpreferences\x18\x01 \x03(\x0b\x32\x31.noteflow.GetPreferencesResponse.PreferencesEntry\x12\x12\n\nupdated_at\x18\x02 \x01(\x01\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x1a\x32\n\x10PreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xce\x01\n\x15SetPreferencesRequest\x12\x45\n\x0bpreferences\x18\x01 \x03(\x0b\x32\x30.noteflow.SetPreferencesRequest.PreferencesEntry\x12\x10\n\x08if_match\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_updated_at\x18\x03 \x01(\x01\x12\r\n\x05merge\x18\x04 \x01(\x08\x1a\x32\n\x10PreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8d\x02\n\x16SetPreferencesResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x10\n\x08\x63onflict\x18\x02 \x01(\x08\x12S\n\x12server_preferences\x18\x03 \x03(\x0b\x32\x37.noteflow.SetPreferencesResponse.ServerPreferencesEntry\x12\x19\n\x11server_updated_at\x18\x04 \x01(\x01\x12\x0c\n\x04\x65tag\x18\x05 \x01(\t\x12\x18\n\x10\x63onflict_message\x18\x06 \x01(\t\x1a\x38\n\x16ServerPreferencesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"5\n\x1bStartIntegrationSyncRequest\x12\x16\n\x0eintegration_id\x18\x01 \x01(\t\"C\n\x1cStartIntegrationSyncResponse\x12\x13\n\x0bsync_run_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t\"+\n\x14GetSyncStatusRequest\x12\x13\n\x0bsync_run_id\x18\x01 \x01(\t\"\xf0\x01\n\x15GetSyncStatusResponse\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x14\n\x0citems_synced\x18\x02 \x01(\x05\x12\x13\n\x0bitems_total\x18\x03 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x05 \x01(\x03\x12+\n\nerror_code\x18\x06 \x01(\x0e\x32\x17.noteflow.SyncErrorCode\x12\x17\n\nexpires_at\x18\n \x01(\tH\x00\x88\x01\x01\x12\x1d\n\x10not_found_reason\x18\x0b \x01(\tH\x01\x88\x01\x01\x42\r\n\x0b_expires_atB\x13\n\x11_not_found_reason\"O\n\x16ListSyncHistoryRequest\x12\x16\n\x0eintegration_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"T\n\x17ListSyncHistoryResponse\x12$\n\x04runs\x18\x01 \x03(\x0b\x32\x16.noteflow.SyncRunProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xc4\x01\n\x0cSyncRunProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x16\n\x0eintegration_id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x14\n\x0citems_synced\x18\x04 \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x06 \x01(\x03\x12\x12\n\nstarted_at\x18\x07 \x01(\t\x12\x14\n\x0c\x63ompleted_at\x18\x08 \x01(\t\x12+\n\nerror_code\x18\t \x01(\x0e\x32\x17.noteflow.SyncErrorCode\"\x1c\n\x1aGetUserIntegrationsRequest\"_\n\x0fIntegrationInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x05 \x01(\t\"N\n\x1bGetUserIntegrationsResponse\x12/\n\x0cintegrations\x18\x01 \x03(\x0b\x32\x19.noteflow.IntegrationInfo\"D\n\x14GetRecentLogsRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\r\n\x05level\x18\x02 \x01(\t\x12\x0e\n\x06source\x18\x03 \x01(\t\">\n\x15GetRecentLogsResponse\x12%\n\x04logs\x18\x01 \x03(\x0b\x32\x17.noteflow.LogEntryProto\"\x99\x02\n\rLogEntryProto\x12\x11\n\ttimestamp\x18\x01 \x01(\t\x12\r\n\x05level\x18\x02 \x01(\t\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\t\x12\x35\n\x07\x64\x65tails\x18\x05 \x03(\x0b\x32$.noteflow.LogEntryProto.DetailsEntry\x12\x10\n\x08trace_id\x18\x06 \x01(\t\x12\x0f\n\x07span_id\x18\x07 \x01(\t\x12\x12\n\nevent_type\x18\x08 \x01(\t\x12\x14\n\x0coperation_id\x18\t \x01(\t\x12\x11\n\tentity_id\x18\n \x01(\t\x1a.\n\x0c\x44\x65tailsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"5\n\x1cGetPerformanceMetricsRequest\x12\x15\n\rhistory_limit\x18\x01 \x01(\x05\"\x87\x01\n\x1dGetPerformanceMetricsResponse\x12\x32\n\x07\x63urrent\x18\x01 \x01(\x0b\x32!.noteflow.PerformanceMetricsPoint\x12\x32\n\x07history\x18\x02 \x03(\x0b\x32!.noteflow.PerformanceMetricsPoint\"\xf1\x01\n\x17PerformanceMetricsPoint\x12\x11\n\ttimestamp\x18\x01 \x01(\x01\x12\x13\n\x0b\x63pu_percent\x18\x02 \x01(\x01\x12\x16\n\x0ememory_percent\x18\x03 \x01(\x01\x12\x11\n\tmemory_mb\x18\x04 \x01(\x01\x12\x14\n\x0c\x64isk_percent\x18\x05 \x01(\x01\x12\x1a\n\x12network_bytes_sent\x18\x06 \x01(\x03\x12\x1a\n\x12network_bytes_recv\x18\x07 \x01(\x03\x12\x19\n\x11process_memory_mb\x18\x08 \x01(\x01\x12\x1a\n\x12\x61\x63tive_connections\x18\t \x01(\x05\"\xd0\x02\n\x11\x43laimMappingProto\x12\x15\n\rsubject_claim\x18\x01 \x01(\t\x12\x13\n\x0b\x65mail_claim\x18\x02 \x01(\t\x12\x1c\n\x14\x65mail_verified_claim\x18\x03 \x01(\t\x12\x12\n\nname_claim\x18\x04 \x01(\t\x12 \n\x18preferred_username_claim\x18\x05 \x01(\t\x12\x14\n\x0cgroups_claim\x18\x06 \x01(\t\x12\x15\n\rpicture_claim\x18\x07 \x01(\t\x12\x1d\n\x10\x66irst_name_claim\x18\x08 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0flast_name_claim\x18\t \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bphone_claim\x18\n \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_first_name_claimB\x12\n\x10_last_name_claimB\x0e\n\x0c_phone_claim\"\xf7\x02\n\x12OidcDiscoveryProto\x12\x0e\n\x06issuer\x18\x01 \x01(\t\x12\x1e\n\x16\x61uthorization_endpoint\x18\x02 \x01(\t\x12\x16\n\x0etoken_endpoint\x18\x03 \x01(\t\x12\x1e\n\x11userinfo_endpoint\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08jwks_uri\x18\x05 \x01(\tH\x01\x88\x01\x01\x12!\n\x14\x65nd_session_endpoint\x18\x06 \x01(\tH\x02\x88\x01\x01\x12 \n\x13revocation_endpoint\x18\x07 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x10scopes_supported\x18\x08 \x03(\t\x12\x18\n\x10\x63laims_supported\x18\t \x03(\t\x12\x15\n\rsupports_pkce\x18\n \x01(\x08\x42\x14\n\x12_userinfo_endpointB\x0b\n\t_jwks_uriB\x17\n\x15_end_session_endpointB\x16\n\x14_revocation_endpoint\"\xc5\x03\n\x11OidcProviderProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06preset\x18\x04 \x01(\t\x12\x12\n\nissuer_url\x18\x05 \x01(\t\x12\x11\n\tclient_id\x18\x06 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x07 \x01(\x08\x12\x34\n\tdiscovery\x18\x08 \x01(\x0b\x32\x1c.noteflow.OidcDiscoveryProtoH\x00\x88\x01\x01\x12\x32\n\rclaim_mapping\x18\t \x01(\x0b\x32\x1b.noteflow.ClaimMappingProto\x12\x0e\n\x06scopes\x18\n \x03(\t\x12\x1e\n\x16require_email_verified\x18\x0b \x01(\x08\x12\x16\n\x0e\x61llowed_groups\x18\x0c \x03(\t\x12\x12\n\ncreated_at\x18\r \x01(\x03\x12\x12\n\nupdated_at\x18\x0e \x01(\x03\x12#\n\x16\x64iscovery_refreshed_at\x18\x0f \x01(\x03H\x01\x88\x01\x01\x12\x10\n\x08warnings\x18\x10 \x03(\tB\x0c\n\n_discoveryB\x19\n\x17_discovery_refreshed_at\"\xf0\x02\n\x1bRegisterOidcProviderRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\nissuer_url\x18\x03 \x01(\t\x12\x11\n\tclient_id\x18\x04 \x01(\t\x12\x1a\n\rclient_secret\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06preset\x18\x06 \x01(\t\x12\x0e\n\x06scopes\x18\x07 \x03(\t\x12\x37\n\rclaim_mapping\x18\x08 \x01(\x0b\x32\x1b.noteflow.ClaimMappingProtoH\x01\x88\x01\x01\x12\x16\n\x0e\x61llowed_groups\x18\t \x03(\t\x12#\n\x16require_email_verified\x18\n \x01(\x08H\x02\x88\x01\x01\x12\x15\n\rauto_discover\x18\x0b \x01(\x08\x42\x10\n\x0e_client_secretB\x10\n\x0e_claim_mappingB\x19\n\x17_require_email_verified\"\\\n\x18ListOidcProvidersRequest\x12\x19\n\x0cworkspace_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0c\x65nabled_only\x18\x02 \x01(\x08\x42\x0f\n\r_workspace_id\"`\n\x19ListOidcProvidersResponse\x12.\n\tproviders\x18\x01 \x03(\x0b\x32\x1b.noteflow.OidcProviderProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"-\n\x16GetOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"\xa1\x02\n\x19UpdateOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06scopes\x18\x03 \x03(\t\x12\x37\n\rclaim_mapping\x18\x04 \x01(\x0b\x32\x1b.noteflow.ClaimMappingProtoH\x01\x88\x01\x01\x12\x16\n\x0e\x61llowed_groups\x18\x05 \x03(\t\x12#\n\x16require_email_verified\x18\x06 \x01(\x08H\x02\x88\x01\x01\x12\x14\n\x07\x65nabled\x18\x07 \x01(\x08H\x03\x88\x01\x01\x42\x07\n\x05_nameB\x10\n\x0e_claim_mappingB\x19\n\x17_require_email_verifiedB\n\n\x08_enabled\"0\n\x19\x44\x65leteOidcProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"-\n\x1a\x44\x65leteOidcProviderResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"s\n\x1bRefreshOidcDiscoveryRequest\x12\x18\n\x0bprovider_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cworkspace_id\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x0e\n\x0c_provider_idB\x0f\n\r_workspace_id\"\xc2\x01\n\x1cRefreshOidcDiscoveryResponse\x12\x44\n\x07results\x18\x01 \x03(\x0b\x32\x33.noteflow.RefreshOidcDiscoveryResponse.ResultsEntry\x12\x15\n\rsuccess_count\x18\x02 \x01(\x05\x12\x15\n\rfailure_count\x18\x03 \x01(\x05\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x18\n\x16ListOidcPresetsRequest\"\xb8\x01\n\x0fOidcPresetProto\x12\x0e\n\x06preset\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x16\n\x0e\x64\x65\x66\x61ult_scopes\x18\x04 \x03(\t\x12\x1e\n\x11\x64ocumentation_url\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05notes\x18\x06 \x01(\tH\x01\x88\x01\x01\x42\x14\n\x12_documentation_urlB\x08\n\x06_notes\"E\n\x17ListOidcPresetsResponse\x12*\n\x07presets\x18\x01 \x03(\x0b\x32\x19.noteflow.OidcPresetProto\"\xea\x01\n\x10\x45xportRulesProto\x12\x33\n\x0e\x64\x65\x66\x61ult_format\x18\x01 \x01(\x0e\x32\x16.noteflow.ExportFormatH\x00\x88\x01\x01\x12\x1a\n\rinclude_audio\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x1f\n\x12include_timestamps\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x18\n\x0btemplate_id\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x11\n\x0f_default_formatB\x10\n\x0e_include_audioB\x15\n\x13_include_timestampsB\x0e\n\x0c_template_id\"\x88\x01\n\x11TriggerRulesProto\x12\x1f\n\x12\x61uto_start_enabled\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x1f\n\x17\x63\x61lendar_match_patterns\x18\x02 \x03(\t\x12\x1a\n\x12\x61pp_match_patterns\x18\x03 \x03(\tB\x15\n\x13_auto_start_enabled\"\xa5\x02\n\x16WorkspaceSettingsProto\x12\x35\n\x0c\x65xport_rules\x18\x01 \x01(\x0b\x32\x1a.noteflow.ExportRulesProtoH\x00\x88\x01\x01\x12\x37\n\rtrigger_rules\x18\x02 \x01(\x0b\x32\x1b.noteflow.TriggerRulesProtoH\x01\x88\x01\x01\x12\x18\n\x0brag_enabled\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12+\n\x1e\x64\x65\x66\x61ult_summarization_template\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x0f\n\r_export_rulesB\x10\n\x0e_trigger_rulesB\x0e\n\x0c_rag_enabledB!\n\x1f_default_summarization_template\"\xa3\x02\n\x14ProjectSettingsProto\x12\x35\n\x0c\x65xport_rules\x18\x01 \x01(\x0b\x32\x1a.noteflow.ExportRulesProtoH\x00\x88\x01\x01\x12\x37\n\rtrigger_rules\x18\x02 \x01(\x0b\x32\x1b.noteflow.TriggerRulesProtoH\x01\x88\x01\x01\x12\x18\n\x0brag_enabled\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12+\n\x1e\x64\x65\x66\x61ult_summarization_template\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x0f\n\r_export_rulesB\x10\n\x0e_trigger_rulesB\x0e\n\x0c_rag_enabledB!\n\x1f_default_summarization_template\"\xc3\x02\n\x0cProjectProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\x04slug\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x12\n\nis_default\x18\x06 \x01(\x08\x12\x13\n\x0bis_archived\x18\x07 \x01(\x08\x12\x35\n\x08settings\x18\x08 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\t \x01(\x03\x12\x12\n\nupdated_at\x18\n \x01(\x03\x12\x18\n\x0b\x61rchived_at\x18\x0b \x01(\x03H\x03\x88\x01\x01\x42\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settingsB\x0e\n\x0c_archived_at\"z\n\x16ProjectMembershipProto\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\x12\x11\n\tjoined_at\x18\x04 \x01(\x03\"\xc4\x01\n\x14\x43reateProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\x04slug\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x35\n\x08settings\x18\x05 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x02\x88\x01\x01\x42\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settings\"\'\n\x11GetProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"=\n\x17GetProjectBySlugRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x0c\n\x04slug\x18\x02 \x01(\t\"d\n\x13ListProjectsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x18\n\x10include_archived\x18\x02 \x01(\x08\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\"U\n\x14ListProjectsResponse\x12(\n\x08projects\x18\x01 \x03(\x0b\x32\x16.noteflow.ProjectProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xd0\x01\n\x14UpdateProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04slug\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x08settings\x18\x05 \x01(\x0b\x32\x1e.noteflow.ProjectSettingsProtoH\x03\x88\x01\x01\x42\x07\n\x05_nameB\x07\n\x05_slugB\x0e\n\x0c_descriptionB\x0b\n\t_settings\"+\n\x15\x41rchiveProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"+\n\x15RestoreProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"*\n\x14\x44\x65leteProjectRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\"(\n\x15\x44\x65leteProjectResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"C\n\x17SetActiveProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x12\n\nproject_id\x18\x02 \x01(\t\"\x1a\n\x18SetActiveProjectResponse\"/\n\x17GetActiveProjectRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"k\n\x18GetActiveProjectResponse\x12\x17\n\nproject_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\'\n\x07project\x18\x02 \x01(\x0b\x32\x16.noteflow.ProjectProtoB\r\n\x0b_project_id\"h\n\x17\x41\x64\x64ProjectMemberRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\"o\n\x1eUpdateProjectMemberRoleRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\x12(\n\x04role\x18\x03 \x01(\x0e\x32\x1a.noteflow.ProjectRoleProto\"A\n\x1aRemoveProjectMemberRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x0f\n\x07user_id\x18\x02 \x01(\t\".\n\x1bRemoveProjectMemberResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"N\n\x19ListProjectMembersRequest\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"d\n\x1aListProjectMembersResponse\x12\x31\n\x07members\x18\x01 \x03(\x0b\x32 .noteflow.ProjectMembershipProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x17\n\x15GetCurrentUserRequest\"\xbb\x01\n\x16GetCurrentUserResponse\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\x12\r\n\x05\x65mail\x18\x04 \x01(\t\x12\x18\n\x10is_authenticated\x18\x05 \x01(\x08\x12\x15\n\rauth_provider\x18\x06 \x01(\t\x12\x16\n\x0eworkspace_name\x18\x07 \x01(\t\x12\x0c\n\x04role\x18\x08 \x01(\t\"Z\n\x0eWorkspaceProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04slug\x18\x03 \x01(\t\x12\x12\n\nis_default\x18\x04 \x01(\x08\x12\x0c\n\x04role\x18\x05 \x01(\t\"6\n\x15ListWorkspacesRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\"[\n\x16ListWorkspacesResponse\x12,\n\nworkspaces\x18\x01 \x03(\x0b\x32\x18.noteflow.WorkspaceProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\".\n\x16SwitchWorkspaceRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"n\n\x17SwitchWorkspaceResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12+\n\tworkspace\x18\x02 \x01(\x0b\x32\x18.noteflow.WorkspaceProto\x12\x15\n\rerror_message\x18\x03 \x01(\t\"3\n\x1bGetWorkspaceSettingsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\"j\n\x1eUpdateWorkspaceSettingsRequest\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x32\n\x08settings\x18\x02 \x01(\x0b\x32 .noteflow.WorkspaceSettingsProto\"\xfa\x01\n\tTaskProto\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nmeeting_id\x18\x02 \x01(\t\x12\x16\n\x0e\x61\x63tion_item_id\x18\x03 \x01(\x05\x12\x0c\n\x04text\x18\x04 \x01(\t\x12)\n\x06status\x18\x05 \x01(\x0e\x32\x19.noteflow.TaskStatusProto\x12\x1a\n\x12\x61ssignee_person_id\x18\x06 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x07 \x01(\x01\x12\x10\n\x08priority\x18\x08 \x01(\x05\x12\x14\n\x0c\x63ompleted_at\x18\t \x01(\x01\x12\x12\n\ncreated_at\x18\n \x01(\x01\x12\x12\n\nupdated_at\x18\x0b \x01(\x01\"\x80\x01\n\x14TaskWithMeetingProto\x12!\n\x04task\x18\x01 \x01(\x0b\x32\x13.noteflow.TaskProto\x12\x15\n\rmeeting_title\x18\x02 \x01(\t\x12\x1a\n\x12meeting_created_at\x18\x03 \x01(\x01\x12\x12\n\nproject_id\x18\x04 \x01(\t\"\xc3\x01\n\x10ListTasksRequest\x12+\n\x08statuses\x18\x01 \x03(\x0e\x32\x19.noteflow.TaskStatusProto\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\x12\x17\n\nproject_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x05 \x03(\t\x12\x17\n\nmeeting_id\x18\x06 \x01(\tH\x01\x88\x01\x01\x42\r\n\x0b_project_idB\r\n\x0b_meeting_id\"W\n\x11ListTasksResponse\x12-\n\x05tasks\x18\x01 \x03(\x0b\x32\x1e.noteflow.TaskWithMeetingProto\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\x9d\x01\n\x11UpdateTaskRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12)\n\x06status\x18\x03 \x01(\x0e\x32\x19.noteflow.TaskStatusProto\x12\x1a\n\x12\x61ssignee_person_id\x18\x04 \x01(\t\x12\x10\n\x08\x64ue_date\x18\x05 \x01(\x01\x12\x10\n\x08priority\x18\x06 \x01(\x05\"7\n\x12UpdateTaskResponse\x12!\n\x04task\x18\x01 \x01(\x0b\x32\x13.noteflow.TaskProto\"\x80\x01\n\x1bGetAnalyticsOverviewRequest\x12\x12\n\nstart_time\x18\x01 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x01\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x04 \x03(\tB\r\n\x0b_project_id\"d\n\x16\x44\x61ilyMeetingStatsProto\x12\x0c\n\x04\x64\x61te\x18\x01 \x01(\t\x12\x10\n\x08meetings\x18\x02 \x01(\x05\x12\x16\n\x0etotal_duration\x18\x03 \x01(\x01\x12\x12\n\nword_count\x18\x04 \x01(\x05\"\xc3\x01\n\x1cGetAnalyticsOverviewResponse\x12/\n\x05\x64\x61ily\x18\x01 \x03(\x0b\x32 .noteflow.DailyMeetingStatsProto\x12\x16\n\x0etotal_meetings\x18\x02 \x01(\x05\x12\x16\n\x0etotal_duration\x18\x03 \x01(\x01\x12\x13\n\x0btotal_words\x18\x04 \x01(\x05\x12\x16\n\x0etotal_segments\x18\x05 \x01(\x05\x12\x15\n\rspeaker_count\x18\x06 \x01(\x05\"\x96\x01\n\x10SpeakerStatProto\x12\x12\n\nspeaker_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x12\n\ntotal_time\x18\x03 \x01(\x01\x12\x15\n\rsegment_count\x18\x04 \x01(\x05\x12\x15\n\rmeeting_count\x18\x05 \x01(\x05\x12\x16\n\x0e\x61vg_confidence\x18\x06 \x01(\x01\"|\n\x17ListSpeakerStatsRequest\x12\x12\n\nstart_time\x18\x01 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x01\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x04 \x03(\tB\r\n\x0b_project_id\"H\n\x18ListSpeakerStatsResponse\x12,\n\x08speakers\x18\x01 \x03(\x0b\x32\x1a.noteflow.SpeakerStatProto\"R\n\x17\x45ntityCategoryStatProto\x12\x10\n\x08\x63\x61tegory\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\x12\x16\n\x0etotal_mentions\x18\x03 \x01(\x05\"^\n\x0eTopEntityProto\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x02 \x01(\t\x12\x15\n\rmention_count\x18\x03 \x01(\x05\x12\x15\n\rmeeting_count\x18\x04 \x01(\x05\"\x91\x01\n\x19GetEntityAnalyticsRequest\x12\x12\n\nstart_time\x18\x01 \x01(\x01\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x01\x12\x17\n\nproject_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0bproject_ids\x18\x04 \x03(\t\x12\x11\n\ttop_limit\x18\x05 \x01(\x05\x42\r\n\x0b_project_id\"\xb4\x01\n\x1aGetEntityAnalyticsResponse\x12\x36\n\x0b\x62y_category\x18\x01 \x03(\x0b\x32!.noteflow.EntityCategoryStatProto\x12.\n\x0ctop_entities\x18\x02 \x03(\x0b\x32\x18.noteflow.TopEntityProto\x12\x16\n\x0etotal_entities\x18\x03 \x01(\x05\x12\x16\n\x0etotal_mentions\x18\x04 \x01(\x05*\x8d\x01\n\nUpdateType\x12\x1b\n\x17UPDATE_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13UPDATE_TYPE_PARTIAL\x10\x01\x12\x15\n\x11UPDATE_TYPE_FINAL\x10\x02\x12\x19\n\x15UPDATE_TYPE_VAD_START\x10\x03\x12\x17\n\x13UPDATE_TYPE_VAD_END\x10\x04*\xb6\x01\n\x0cMeetingState\x12\x1d\n\x19MEETING_STATE_UNSPECIFIED\x10\x00\x12\x19\n\x15MEETING_STATE_CREATED\x10\x01\x12\x1b\n\x17MEETING_STATE_RECORDING\x10\x02\x12\x19\n\x15MEETING_STATE_STOPPED\x10\x03\x12\x1b\n\x17MEETING_STATE_COMPLETED\x10\x04\x12\x17\n\x13MEETING_STATE_ERROR\x10\x05*`\n\tSortOrder\x12\x1a\n\x16SORT_ORDER_UNSPECIFIED\x10\x00\x12\x1b\n\x17SORT_ORDER_CREATED_DESC\x10\x01\x12\x1a\n\x16SORT_ORDER_CREATED_ASC\x10\x02*^\n\x08Priority\x12\x18\n\x14PRIORITY_UNSPECIFIED\x10\x00\x12\x10\n\x0cPRIORITY_LOW\x10\x01\x12\x13\n\x0fPRIORITY_MEDIUM\x10\x02\x12\x11\n\rPRIORITY_HIGH\x10\x03*e\n\tAsrDevice\x12\x1a\n\x16\x41SR_DEVICE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x41SR_DEVICE_CPU\x10\x01\x12\x13\n\x0f\x41SR_DEVICE_CUDA\x10\x02\x12\x13\n\x0f\x41SR_DEVICE_ROCM\x10\x03*\x89\x01\n\x0e\x41srComputeType\x12 \n\x1c\x41SR_COMPUTE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SR_COMPUTE_TYPE_INT8\x10\x01\x12\x1c\n\x18\x41SR_COMPUTE_TYPE_FLOAT16\x10\x02\x12\x1c\n\x18\x41SR_COMPUTE_TYPE_FLOAT32\x10\x03*\xa4\x01\n\x0e\x41nnotationType\x12\x1f\n\x1b\x41NNOTATION_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x41NNOTATION_TYPE_ACTION_ITEM\x10\x01\x12\x1c\n\x18\x41NNOTATION_TYPE_DECISION\x10\x02\x12\x18\n\x14\x41NNOTATION_TYPE_NOTE\x10\x03\x12\x18\n\x14\x41NNOTATION_TYPE_RISK\x10\x04*x\n\x0c\x45xportFormat\x12\x1d\n\x19\x45XPORT_FORMAT_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x45XPORT_FORMAT_MARKDOWN\x10\x01\x12\x16\n\x12\x45XPORT_FORMAT_HTML\x10\x02\x12\x15\n\x11\x45XPORT_FORMAT_PDF\x10\x03*\xa1\x01\n\tJobStatus\x12\x1a\n\x16JOB_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11JOB_STATUS_QUEUED\x10\x01\x12\x16\n\x12JOB_STATUS_RUNNING\x10\x02\x12\x18\n\x14JOB_STATUS_COMPLETED\x10\x03\x12\x15\n\x11JOB_STATUS_FAILED\x10\x04\x12\x18\n\x14JOB_STATUS_CANCELLED\x10\x05*\xb8\x01\n\rSyncErrorCode\x12\x1f\n\x1bSYNC_ERROR_CODE_UNSPECIFIED\x10\x00\x12!\n\x1dSYNC_ERROR_CODE_AUTH_REQUIRED\x10\x01\x12\"\n\x1eSYNC_ERROR_CODE_PROVIDER_ERROR\x10\x02\x12\"\n\x1eSYNC_ERROR_CODE_INTERNAL_ERROR\x10\x03\x12\x1b\n\x17SYNC_ERROR_CODE_UNKNOWN\x10\x04*\xc9\x01\n\x14ProcessingStepStatus\x12\x1f\n\x1bPROCESSING_STEP_UNSPECIFIED\x10\x00\x12\x1b\n\x17PROCESSING_STEP_PENDING\x10\x01\x12\x1b\n\x17PROCESSING_STEP_RUNNING\x10\x02\x12\x1d\n\x19PROCESSING_STEP_COMPLETED\x10\x03\x12\x1a\n\x16PROCESSING_STEP_FAILED\x10\x04\x12\x1b\n\x17PROCESSING_STEP_SKIPPED\x10\x05*z\n\x10ProjectRoleProto\x12\x1c\n\x18PROJECT_ROLE_UNSPECIFIED\x10\x00\x12\x17\n\x13PROJECT_ROLE_VIEWER\x10\x01\x12\x17\n\x13PROJECT_ROLE_EDITOR\x10\x02\x12\x16\n\x12PROJECT_ROLE_ADMIN\x10\x03*u\n\x0fTaskStatusProto\x12\x1b\n\x17TASK_STATUS_UNSPECIFIED\x10\x00\x12\x14\n\x10TASK_STATUS_OPEN\x10\x01\x12\x14\n\x10TASK_STATUS_DONE\x10\x02\x12\x19\n\x15TASK_STATUS_DISMISSED\x10\x03\x32\xdd\x43\n\x0fNoteFlowService\x12K\n\x13StreamTranscription\x12\x14.noteflow.AudioChunk\x1a\x1a.noteflow.TranscriptUpdate(\x01\x30\x01\x12\x42\n\rCreateMeeting\x12\x1e.noteflow.CreateMeetingRequest\x1a\x11.noteflow.Meeting\x12>\n\x0bStopMeeting\x12\x1c.noteflow.StopMeetingRequest\x1a\x11.noteflow.Meeting\x12M\n\x0cListMeetings\x12\x1d.noteflow.ListMeetingsRequest\x1a\x1e.noteflow.ListMeetingsResponse\x12<\n\nGetMeeting\x12\x1b.noteflow.GetMeetingRequest\x1a\x11.noteflow.Meeting\x12P\n\rDeleteMeeting\x12\x1e.noteflow.DeleteMeetingRequest\x1a\x1f.noteflow.DeleteMeetingResponse\x12\x46\n\x0fGenerateSummary\x12 .noteflow.GenerateSummaryRequest\x1a\x11.noteflow.Summary\x12w\n\x1aListSummarizationTemplates\x12+.noteflow.ListSummarizationTemplatesRequest\x1a,.noteflow.ListSummarizationTemplatesResponse\x12q\n\x18GetSummarizationTemplate\x12).noteflow.GetSummarizationTemplateRequest\x1a*.noteflow.GetSummarizationTemplateResponse\x12|\n\x1b\x43reateSummarizationTemplate\x12,.noteflow.CreateSummarizationTemplateRequest\x1a/.noteflow.SummarizationTemplateMutationResponse\x12|\n\x1bUpdateSummarizationTemplate\x12,.noteflow.UpdateSummarizationTemplateRequest\x1a/.noteflow.SummarizationTemplateMutationResponse\x12s\n\x1c\x41rchiveSummarizationTemplate\x12-.noteflow.ArchiveSummarizationTemplateRequest\x1a$.noteflow.SummarizationTemplateProto\x12\x8c\x01\n!ListSummarizationTemplateVersions\x12\x32.noteflow.ListSummarizationTemplateVersionsRequest\x1a\x33.noteflow.ListSummarizationTemplateVersionsResponse\x12\x81\x01\n#RestoreSummarizationTemplateVersion\x12\x34.noteflow.RestoreSummarizationTemplateVersionRequest\x1a$.noteflow.SummarizationTemplateProto\x12\x45\n\rAddAnnotation\x12\x1e.noteflow.AddAnnotationRequest\x1a\x14.noteflow.Annotation\x12\x45\n\rGetAnnotation\x12\x1e.noteflow.GetAnnotationRequest\x1a\x14.noteflow.Annotation\x12V\n\x0fListAnnotations\x12 .noteflow.ListAnnotationsRequest\x1a!.noteflow.ListAnnotationsResponse\x12K\n\x10UpdateAnnotation\x12!.noteflow.UpdateAnnotationRequest\x1a\x14.noteflow.Annotation\x12Y\n\x10\x44\x65leteAnnotation\x12!.noteflow.DeleteAnnotationRequest\x1a\".noteflow.DeleteAnnotationResponse\x12Y\n\x10\x45xportTranscript\x12!.noteflow.ExportTranscriptRequest\x1a\".noteflow.ExportTranscriptResponse\x12q\n\x18RefineSpeakerDiarization\x12).noteflow.RefineSpeakerDiarizationRequest\x1a*.noteflow.RefineSpeakerDiarizationResponse\x12P\n\rRenameSpeaker\x12\x1e.noteflow.RenameSpeakerRequest\x1a\x1f.noteflow.RenameSpeakerResponse\x12\x63\n\x17GetDiarizationJobStatus\x12(.noteflow.GetDiarizationJobStatusRequest\x1a\x1e.noteflow.DiarizationJobStatus\x12\x65\n\x14\x43\x61ncelDiarizationJob\x12%.noteflow.CancelDiarizationJobRequest\x1a&.noteflow.CancelDiarizationJobResponse\x12q\n\x18GetActiveDiarizationJobs\x12).noteflow.GetActiveDiarizationJobsRequest\x1a*.noteflow.GetActiveDiarizationJobsResponse\x12\x42\n\rGetServerInfo\x12\x1b.noteflow.ServerInfoRequest\x1a\x14.noteflow.ServerInfo\x12\x62\n\x13GetAsrConfiguration\x12$.noteflow.GetAsrConfigurationRequest\x1a%.noteflow.GetAsrConfigurationResponse\x12k\n\x16UpdateAsrConfiguration\x12\'.noteflow.UpdateAsrConfigurationRequest\x1a(.noteflow.UpdateAsrConfigurationResponse\x12r\n\x1cGetAsrConfigurationJobStatus\x12-.noteflow.GetAsrConfigurationJobStatusRequest\x1a#.noteflow.AsrConfigurationJobStatus\x12t\n\x19GetStreamingConfiguration\x12*.noteflow.GetStreamingConfigurationRequest\x1a+.noteflow.GetStreamingConfigurationResponse\x12}\n\x1cUpdateStreamingConfiguration\x12-.noteflow.UpdateStreamingConfigurationRequest\x1a..noteflow.UpdateStreamingConfigurationResponse\x12V\n\x0f\x45xtractEntities\x12 .noteflow.ExtractEntitiesRequest\x1a!.noteflow.ExtractEntitiesResponse\x12M\n\x0cUpdateEntity\x12\x1d.noteflow.UpdateEntityRequest\x1a\x1e.noteflow.UpdateEntityResponse\x12M\n\x0c\x44\x65leteEntity\x12\x1d.noteflow.DeleteEntityRequest\x1a\x1e.noteflow.DeleteEntityResponse\x12_\n\x12ListCalendarEvents\x12#.noteflow.ListCalendarEventsRequest\x1a$.noteflow.ListCalendarEventsResponse\x12\x65\n\x14GetCalendarProviders\x12%.noteflow.GetCalendarProvidersRequest\x1a&.noteflow.GetCalendarProvidersResponse\x12P\n\rInitiateOAuth\x12\x1e.noteflow.InitiateOAuthRequest\x1a\x1f.noteflow.InitiateOAuthResponse\x12P\n\rCompleteOAuth\x12\x1e.noteflow.CompleteOAuthRequest\x1a\x1f.noteflow.CompleteOAuthResponse\x12q\n\x18GetOAuthConnectionStatus\x12).noteflow.GetOAuthConnectionStatusRequest\x1a*.noteflow.GetOAuthConnectionStatusResponse\x12V\n\x0f\x44isconnectOAuth\x12 .noteflow.DisconnectOAuthRequest\x1a!.noteflow.DisconnectOAuthResponse\x12\x65\n\x14GetOAuthClientConfig\x12%.noteflow.GetOAuthClientConfigRequest\x1a&.noteflow.GetOAuthClientConfigResponse\x12\x65\n\x14SetOAuthClientConfig\x12%.noteflow.SetOAuthClientConfigRequest\x1a&.noteflow.SetOAuthClientConfigResponse\x12Q\n\x0fRegisterWebhook\x12 .noteflow.RegisterWebhookRequest\x1a\x1c.noteflow.WebhookConfigProto\x12M\n\x0cListWebhooks\x12\x1d.noteflow.ListWebhooksRequest\x1a\x1e.noteflow.ListWebhooksResponse\x12M\n\rUpdateWebhook\x12\x1e.noteflow.UpdateWebhookRequest\x1a\x1c.noteflow.WebhookConfigProto\x12P\n\rDeleteWebhook\x12\x1e.noteflow.DeleteWebhookRequest\x1a\x1f.noteflow.DeleteWebhookResponse\x12\x65\n\x14GetWebhookDeliveries\x12%.noteflow.GetWebhookDeliveriesRequest\x1a&.noteflow.GetWebhookDeliveriesResponse\x12\\\n\x11GrantCloudConsent\x12\".noteflow.GrantCloudConsentRequest\x1a#.noteflow.GrantCloudConsentResponse\x12_\n\x12RevokeCloudConsent\x12#.noteflow.RevokeCloudConsentRequest\x1a$.noteflow.RevokeCloudConsentResponse\x12h\n\x15GetCloudConsentStatus\x12&.noteflow.GetCloudConsentStatusRequest\x1a\'.noteflow.GetCloudConsentStatusResponse\x12\x62\n\x13SetHuggingFaceToken\x12$.noteflow.SetHuggingFaceTokenRequest\x1a%.noteflow.SetHuggingFaceTokenResponse\x12t\n\x19GetHuggingFaceTokenStatus\x12*.noteflow.GetHuggingFaceTokenStatusRequest\x1a+.noteflow.GetHuggingFaceTokenStatusResponse\x12k\n\x16\x44\x65leteHuggingFaceToken\x12\'.noteflow.DeleteHuggingFaceTokenRequest\x1a(.noteflow.DeleteHuggingFaceTokenResponse\x12q\n\x18ValidateHuggingFaceToken\x12).noteflow.ValidateHuggingFaceTokenRequest\x1a*.noteflow.ValidateHuggingFaceTokenResponse\x12S\n\x0eGetPreferences\x12\x1f.noteflow.GetPreferencesRequest\x1a .noteflow.GetPreferencesResponse\x12S\n\x0eSetPreferences\x12\x1f.noteflow.SetPreferencesRequest\x1a .noteflow.SetPreferencesResponse\x12\x65\n\x14StartIntegrationSync\x12%.noteflow.StartIntegrationSyncRequest\x1a&.noteflow.StartIntegrationSyncResponse\x12P\n\rGetSyncStatus\x12\x1e.noteflow.GetSyncStatusRequest\x1a\x1f.noteflow.GetSyncStatusResponse\x12V\n\x0fListSyncHistory\x12 .noteflow.ListSyncHistoryRequest\x1a!.noteflow.ListSyncHistoryResponse\x12\x62\n\x13GetUserIntegrations\x12$.noteflow.GetUserIntegrationsRequest\x1a%.noteflow.GetUserIntegrationsResponse\x12P\n\rGetRecentLogs\x12\x1e.noteflow.GetRecentLogsRequest\x1a\x1f.noteflow.GetRecentLogsResponse\x12h\n\x15GetPerformanceMetrics\x12&.noteflow.GetPerformanceMetricsRequest\x1a\'.noteflow.GetPerformanceMetricsResponse\x12Z\n\x14RegisterOidcProvider\x12%.noteflow.RegisterOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12\\\n\x11ListOidcProviders\x12\".noteflow.ListOidcProvidersRequest\x1a#.noteflow.ListOidcProvidersResponse\x12P\n\x0fGetOidcProvider\x12 .noteflow.GetOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12V\n\x12UpdateOidcProvider\x12#.noteflow.UpdateOidcProviderRequest\x1a\x1b.noteflow.OidcProviderProto\x12_\n\x12\x44\x65leteOidcProvider\x12#.noteflow.DeleteOidcProviderRequest\x1a$.noteflow.DeleteOidcProviderResponse\x12\x65\n\x14RefreshOidcDiscovery\x12%.noteflow.RefreshOidcDiscoveryRequest\x1a&.noteflow.RefreshOidcDiscoveryResponse\x12V\n\x0fListOidcPresets\x12 .noteflow.ListOidcPresetsRequest\x1a!.noteflow.ListOidcPresetsResponse\x12G\n\rCreateProject\x12\x1e.noteflow.CreateProjectRequest\x1a\x16.noteflow.ProjectProto\x12\x41\n\nGetProject\x12\x1b.noteflow.GetProjectRequest\x1a\x16.noteflow.ProjectProto\x12M\n\x10GetProjectBySlug\x12!.noteflow.GetProjectBySlugRequest\x1a\x16.noteflow.ProjectProto\x12M\n\x0cListProjects\x12\x1d.noteflow.ListProjectsRequest\x1a\x1e.noteflow.ListProjectsResponse\x12G\n\rUpdateProject\x12\x1e.noteflow.UpdateProjectRequest\x1a\x16.noteflow.ProjectProto\x12I\n\x0e\x41rchiveProject\x12\x1f.noteflow.ArchiveProjectRequest\x1a\x16.noteflow.ProjectProto\x12I\n\x0eRestoreProject\x12\x1f.noteflow.RestoreProjectRequest\x1a\x16.noteflow.ProjectProto\x12P\n\rDeleteProject\x12\x1e.noteflow.DeleteProjectRequest\x1a\x1f.noteflow.DeleteProjectResponse\x12Y\n\x10SetActiveProject\x12!.noteflow.SetActiveProjectRequest\x1a\".noteflow.SetActiveProjectResponse\x12Y\n\x10GetActiveProject\x12!.noteflow.GetActiveProjectRequest\x1a\".noteflow.GetActiveProjectResponse\x12\x44\n\tListTasks\x12\x1a.noteflow.ListTasksRequest\x1a\x1b.noteflow.ListTasksResponse\x12G\n\nUpdateTask\x12\x1b.noteflow.UpdateTaskRequest\x1a\x1c.noteflow.UpdateTaskResponse\x12\x65\n\x14GetAnalyticsOverview\x12%.noteflow.GetAnalyticsOverviewRequest\x1a&.noteflow.GetAnalyticsOverviewResponse\x12Y\n\x10ListSpeakerStats\x12!.noteflow.ListSpeakerStatsRequest\x1a\".noteflow.ListSpeakerStatsResponse\x12_\n\x12GetEntityAnalytics\x12#.noteflow.GetEntityAnalyticsRequest\x1a$.noteflow.GetEntityAnalyticsResponse\x12W\n\x10\x41\x64\x64ProjectMember\x12!.noteflow.AddProjectMemberRequest\x1a .noteflow.ProjectMembershipProto\x12\x65\n\x17UpdateProjectMemberRole\x12(.noteflow.UpdateProjectMemberRoleRequest\x1a .noteflow.ProjectMembershipProto\x12\x62\n\x13RemoveProjectMember\x12$.noteflow.RemoveProjectMemberRequest\x1a%.noteflow.RemoveProjectMemberResponse\x12_\n\x12ListProjectMembers\x12#.noteflow.ListProjectMembersRequest\x1a$.noteflow.ListProjectMembersResponse\x12S\n\x0eGetCurrentUser\x12\x1f.noteflow.GetCurrentUserRequest\x1a .noteflow.GetCurrentUserResponse\x12S\n\x0eListWorkspaces\x12\x1f.noteflow.ListWorkspacesRequest\x1a .noteflow.ListWorkspacesResponse\x12V\n\x0fSwitchWorkspace\x12 .noteflow.SwitchWorkspaceRequest\x1a!.noteflow.SwitchWorkspaceResponse\x12_\n\x14GetWorkspaceSettings\x12%.noteflow.GetWorkspaceSettingsRequest\x1a .noteflow.WorkspaceSettingsProto\x12\x65\n\x17UpdateWorkspaceSettings\x12(.noteflow.UpdateWorkspaceSettingsRequest\x1a .noteflow.WorkspaceSettingsProtob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -45,32 +45,32 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['_LOGENTRYPROTO_DETAILSENTRY']._serialized_options = b'8\001' _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._loaded_options = None _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._serialized_options = b'8\001' - _globals['_UPDATETYPE']._serialized_start=24619 - _globals['_UPDATETYPE']._serialized_end=24760 - _globals['_MEETINGSTATE']._serialized_start=24763 - _globals['_MEETINGSTATE']._serialized_end=24945 - _globals['_SORTORDER']._serialized_start=24947 - _globals['_SORTORDER']._serialized_end=25043 - _globals['_PRIORITY']._serialized_start=25045 - _globals['_PRIORITY']._serialized_end=25139 - _globals['_ASRDEVICE']._serialized_start=25141 - _globals['_ASRDEVICE']._serialized_end=25242 - _globals['_ASRCOMPUTETYPE']._serialized_start=25245 - _globals['_ASRCOMPUTETYPE']._serialized_end=25382 - _globals['_ANNOTATIONTYPE']._serialized_start=25385 - _globals['_ANNOTATIONTYPE']._serialized_end=25549 - _globals['_EXPORTFORMAT']._serialized_start=25551 - _globals['_EXPORTFORMAT']._serialized_end=25671 - _globals['_JOBSTATUS']._serialized_start=25674 - _globals['_JOBSTATUS']._serialized_end=25835 - _globals['_SYNCERRORCODE']._serialized_start=25838 - _globals['_SYNCERRORCODE']._serialized_end=26022 - _globals['_PROCESSINGSTEPSTATUS']._serialized_start=26025 - _globals['_PROCESSINGSTEPSTATUS']._serialized_end=26226 - _globals['_PROJECTROLEPROTO']._serialized_start=26228 - _globals['_PROJECTROLEPROTO']._serialized_end=26350 - _globals['_TASKSTATUSPROTO']._serialized_start=26352 - _globals['_TASKSTATUSPROTO']._serialized_end=26469 + _globals['_UPDATETYPE']._serialized_start=25156 + _globals['_UPDATETYPE']._serialized_end=25297 + _globals['_MEETINGSTATE']._serialized_start=25300 + _globals['_MEETINGSTATE']._serialized_end=25482 + _globals['_SORTORDER']._serialized_start=25484 + _globals['_SORTORDER']._serialized_end=25580 + _globals['_PRIORITY']._serialized_start=25582 + _globals['_PRIORITY']._serialized_end=25676 + _globals['_ASRDEVICE']._serialized_start=25678 + _globals['_ASRDEVICE']._serialized_end=25779 + _globals['_ASRCOMPUTETYPE']._serialized_start=25782 + _globals['_ASRCOMPUTETYPE']._serialized_end=25919 + _globals['_ANNOTATIONTYPE']._serialized_start=25922 + _globals['_ANNOTATIONTYPE']._serialized_end=26086 + _globals['_EXPORTFORMAT']._serialized_start=26088 + _globals['_EXPORTFORMAT']._serialized_end=26208 + _globals['_JOBSTATUS']._serialized_start=26211 + _globals['_JOBSTATUS']._serialized_end=26372 + _globals['_SYNCERRORCODE']._serialized_start=26375 + _globals['_SYNCERRORCODE']._serialized_end=26559 + _globals['_PROCESSINGSTEPSTATUS']._serialized_start=26562 + _globals['_PROCESSINGSTEPSTATUS']._serialized_end=26763 + _globals['_PROJECTROLEPROTO']._serialized_start=26765 + _globals['_PROJECTROLEPROTO']._serialized_end=26887 + _globals['_TASKSTATUSPROTO']._serialized_start=26889 + _globals['_TASKSTATUSPROTO']._serialized_end=27006 _globals['_AUDIOCHUNK']._serialized_start=29 _globals['_AUDIOCHUNK']._serialized_end=163 _globals['_CONGESTIONINFO']._serialized_start=165 @@ -92,399 +92,407 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['_STOPMEETINGREQUEST']._serialized_start=1527 _globals['_STOPMEETINGREQUEST']._serialized_end=1567 _globals['_LISTMEETINGSREQUEST']._serialized_start=1570 - _globals['_LISTMEETINGSREQUEST']._serialized_end=1764 - _globals['_LISTMEETINGSRESPONSE']._serialized_start=1766 - _globals['_LISTMEETINGSRESPONSE']._serialized_end=1846 - _globals['_GETMEETINGREQUEST']._serialized_start=1848 - _globals['_GETMEETINGREQUEST']._serialized_end=1938 - _globals['_DELETEMEETINGREQUEST']._serialized_start=1940 - _globals['_DELETEMEETINGREQUEST']._serialized_end=1982 - _globals['_DELETEMEETINGRESPONSE']._serialized_start=1984 - _globals['_DELETEMEETINGRESPONSE']._serialized_end=2024 - _globals['_SUMMARY']._serialized_start=2027 - _globals['_SUMMARY']._serialized_end=2212 - _globals['_KEYPOINT']._serialized_start=2214 - _globals['_KEYPOINT']._serialized_end=2297 - _globals['_ACTIONITEM']._serialized_start=2299 - _globals['_ACTIONITEM']._serialized_end=2420 - _globals['_SUMMARIZATIONOPTIONS']._serialized_start=2422 - _globals['_SUMMARIZATIONOPTIONS']._serialized_end=2514 - _globals['_GENERATESUMMARYREQUEST']._serialized_start=2516 - _globals['_GENERATESUMMARYREQUEST']._serialized_end=2635 - _globals['_SUMMARIZATIONTEMPLATEPROTO']._serialized_start=2638 - _globals['_SUMMARIZATIONTEMPLATEPROTO']._serialized_end=2994 - _globals['_SUMMARIZATIONTEMPLATEVERSIONPROTO']._serialized_start=2997 - _globals['_SUMMARIZATIONTEMPLATEVERSIONPROTO']._serialized_end=3208 - _globals['_LISTSUMMARIZATIONTEMPLATESREQUEST']._serialized_start=3211 - _globals['_LISTSUMMARIZATIONTEMPLATESREQUEST']._serialized_end=3349 - _globals['_LISTSUMMARIZATIONTEMPLATESRESPONSE']._serialized_start=3351 - _globals['_LISTSUMMARIZATIONTEMPLATESRESPONSE']._serialized_end=3465 - _globals['_GETSUMMARIZATIONTEMPLATEREQUEST']._serialized_start=3467 - _globals['_GETSUMMARIZATIONTEMPLATEREQUEST']._serialized_end=3554 - _globals['_GETSUMMARIZATIONTEMPLATERESPONSE']._serialized_start=3557 - _globals['_GETSUMMARIZATIONTEMPLATERESPONSE']._serialized_end=3742 - _globals['_SUMMARIZATIONTEMPLATEMUTATIONRESPONSE']._serialized_start=3745 - _globals['_SUMMARIZATIONTEMPLATEMUTATIONRESPONSE']._serialized_end=3919 - _globals['_CREATESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=3922 - _globals['_CREATESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4095 - _globals['_UPDATESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=4098 - _globals['_UPDATESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4301 - _globals['_ARCHIVESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=4303 - _globals['_ARCHIVESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4361 - _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSREQUEST']._serialized_start=4363 - _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSREQUEST']._serialized_end=4457 - _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSRESPONSE']._serialized_start=4459 - _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSRESPONSE']._serialized_end=4586 - _globals['_RESTORESUMMARIZATIONTEMPLATEVERSIONREQUEST']._serialized_start=4588 - _globals['_RESTORESUMMARIZATIONTEMPLATEVERSIONREQUEST']._serialized_end=4673 - _globals['_SERVERINFOREQUEST']._serialized_start=4675 - _globals['_SERVERINFOREQUEST']._serialized_end=4694 - _globals['_SERVERINFO']._serialized_start=4697 - _globals['_SERVERINFO']._serialized_end=5212 - _globals['_ASRCONFIGURATION']._serialized_start=5215 - _globals['_ASRCONFIGURATION']._serialized_end=5515 - _globals['_GETASRCONFIGURATIONREQUEST']._serialized_start=5517 - _globals['_GETASRCONFIGURATIONREQUEST']._serialized_end=5545 - _globals['_GETASRCONFIGURATIONRESPONSE']._serialized_start=5547 - _globals['_GETASRCONFIGURATIONRESPONSE']._serialized_end=5627 - _globals['_UPDATEASRCONFIGURATIONREQUEST']._serialized_start=5630 - _globals['_UPDATEASRCONFIGURATIONREQUEST']._serialized_end=5824 - _globals['_UPDATEASRCONFIGURATIONRESPONSE']._serialized_start=5826 - _globals['_UPDATEASRCONFIGURATIONRESPONSE']._serialized_end=5952 - _globals['_GETASRCONFIGURATIONJOBSTATUSREQUEST']._serialized_start=5954 - _globals['_GETASRCONFIGURATIONJOBSTATUSREQUEST']._serialized_end=6007 - _globals['_ASRCONFIGURATIONJOBSTATUS']._serialized_start=6010 - _globals['_ASRCONFIGURATIONJOBSTATUS']._serialized_end=6236 - _globals['_STREAMINGCONFIGURATION']._serialized_start=6239 - _globals['_STREAMINGCONFIGURATION']._serialized_end=6472 - _globals['_GETSTREAMINGCONFIGURATIONREQUEST']._serialized_start=6474 - _globals['_GETSTREAMINGCONFIGURATIONREQUEST']._serialized_end=6508 - _globals['_GETSTREAMINGCONFIGURATIONRESPONSE']._serialized_start=6510 - _globals['_GETSTREAMINGCONFIGURATIONRESPONSE']._serialized_end=6602 - _globals['_UPDATESTREAMINGCONFIGURATIONREQUEST']._serialized_start=6605 - _globals['_UPDATESTREAMINGCONFIGURATIONREQUEST']._serialized_end=7060 - _globals['_UPDATESTREAMINGCONFIGURATIONRESPONSE']._serialized_start=7062 - _globals['_UPDATESTREAMINGCONFIGURATIONRESPONSE']._serialized_end=7157 - _globals['_ANNOTATION']._serialized_start=7160 - _globals['_ANNOTATION']._serialized_end=7348 - _globals['_ADDANNOTATIONREQUEST']._serialized_start=7351 - _globals['_ADDANNOTATIONREQUEST']._serialized_end=7517 - _globals['_GETANNOTATIONREQUEST']._serialized_start=7519 - _globals['_GETANNOTATIONREQUEST']._serialized_end=7564 - _globals['_LISTANNOTATIONSREQUEST']._serialized_start=7566 - _globals['_LISTANNOTATIONSREQUEST']._serialized_end=7648 - _globals['_LISTANNOTATIONSRESPONSE']._serialized_start=7650 - _globals['_LISTANNOTATIONSRESPONSE']._serialized_end=7718 - _globals['_UPDATEANNOTATIONREQUEST']._serialized_start=7721 - _globals['_UPDATEANNOTATIONREQUEST']._serialized_end=7893 - _globals['_DELETEANNOTATIONREQUEST']._serialized_start=7895 - _globals['_DELETEANNOTATIONREQUEST']._serialized_end=7943 - _globals['_DELETEANNOTATIONRESPONSE']._serialized_start=7945 - _globals['_DELETEANNOTATIONRESPONSE']._serialized_end=7988 - _globals['_PROCESSINGSTEPSTATE']._serialized_start=7991 - _globals['_PROCESSINGSTEPSTATE']._serialized_end=8125 - _globals['_PROCESSINGSTATUS']._serialized_start=8128 - _globals['_PROCESSINGSTATUS']._serialized_end=8295 - _globals['_EXPORTTRANSCRIPTREQUEST']._serialized_start=8297 - _globals['_EXPORTTRANSCRIPTREQUEST']._serialized_end=8382 - _globals['_EXPORTTRANSCRIPTRESPONSE']._serialized_start=8384 - _globals['_EXPORTTRANSCRIPTRESPONSE']._serialized_end=8472 - _globals['_REFINESPEAKERDIARIZATIONREQUEST']._serialized_start=8474 - _globals['_REFINESPEAKERDIARIZATIONREQUEST']._serialized_end=8549 - _globals['_REFINESPEAKERDIARIZATIONRESPONSE']._serialized_start=8552 - _globals['_REFINESPEAKERDIARIZATIONRESPONSE']._serialized_end=8709 - _globals['_RENAMESPEAKERREQUEST']._serialized_start=8711 - _globals['_RENAMESPEAKERREQUEST']._serialized_end=8803 - _globals['_RENAMESPEAKERRESPONSE']._serialized_start=8805 - _globals['_RENAMESPEAKERRESPONSE']._serialized_end=8871 - _globals['_GETDIARIZATIONJOBSTATUSREQUEST']._serialized_start=8873 - _globals['_GETDIARIZATIONJOBSTATUSREQUEST']._serialized_end=8921 - _globals['_DIARIZATIONJOBSTATUS']._serialized_start=8924 - _globals['_DIARIZATIONJOBSTATUS']._serialized_end=9095 - _globals['_CANCELDIARIZATIONJOBREQUEST']._serialized_start=9097 - _globals['_CANCELDIARIZATIONJOBREQUEST']._serialized_end=9142 - _globals['_CANCELDIARIZATIONJOBRESPONSE']._serialized_start=9144 - _globals['_CANCELDIARIZATIONJOBRESPONSE']._serialized_end=9251 - _globals['_GETACTIVEDIARIZATIONJOBSREQUEST']._serialized_start=9253 - _globals['_GETACTIVEDIARIZATIONJOBSREQUEST']._serialized_end=9286 - _globals['_GETACTIVEDIARIZATIONJOBSRESPONSE']._serialized_start=9288 - _globals['_GETACTIVEDIARIZATIONJOBSRESPONSE']._serialized_end=9368 - _globals['_EXTRACTENTITIESREQUEST']._serialized_start=9370 - _globals['_EXTRACTENTITIESREQUEST']._serialized_end=9437 - _globals['_EXTRACTEDENTITY']._serialized_start=9439 - _globals['_EXTRACTEDENTITY']._serialized_end=9560 - _globals['_EXTRACTENTITIESRESPONSE']._serialized_start=9562 - _globals['_EXTRACTENTITIESRESPONSE']._serialized_end=9669 - _globals['_UPDATEENTITYREQUEST']._serialized_start=9671 - _globals['_UPDATEENTITYREQUEST']._serialized_end=9763 - _globals['_UPDATEENTITYRESPONSE']._serialized_start=9765 - _globals['_UPDATEENTITYRESPONSE']._serialized_end=9830 - _globals['_DELETEENTITYREQUEST']._serialized_start=9832 - _globals['_DELETEENTITYREQUEST']._serialized_end=9892 - _globals['_DELETEENTITYRESPONSE']._serialized_start=9894 - _globals['_DELETEENTITYRESPONSE']._serialized_end=9933 - _globals['_CALENDAREVENT']._serialized_start=9936 - _globals['_CALENDAREVENT']._serialized_end=10135 - _globals['_LISTCALENDAREVENTSREQUEST']._serialized_start=10137 - _globals['_LISTCALENDAREVENTSREQUEST']._serialized_end=10218 - _globals['_LISTCALENDAREVENTSRESPONSE']._serialized_start=10220 - _globals['_LISTCALENDAREVENTSRESPONSE']._serialized_end=10310 - _globals['_GETCALENDARPROVIDERSREQUEST']._serialized_start=10312 - _globals['_GETCALENDARPROVIDERSREQUEST']._serialized_end=10341 - _globals['_CALENDARPROVIDER']._serialized_start=10343 - _globals['_CALENDARPROVIDER']._serialized_end=10423 - _globals['_GETCALENDARPROVIDERSRESPONSE']._serialized_start=10425 - _globals['_GETCALENDARPROVIDERSRESPONSE']._serialized_end=10502 - _globals['_INITIATEOAUTHREQUEST']._serialized_start=10504 - _globals['_INITIATEOAUTHREQUEST']._serialized_end=10592 - _globals['_INITIATEOAUTHRESPONSE']._serialized_start=10594 - _globals['_INITIATEOAUTHRESPONSE']._serialized_end=10650 - _globals['_COMPLETEOAUTHREQUEST']._serialized_start=10652 - _globals['_COMPLETEOAUTHREQUEST']._serialized_end=10721 - _globals['_COMPLETEOAUTHRESPONSE']._serialized_start=10723 - _globals['_COMPLETEOAUTHRESPONSE']._serialized_end=10834 - _globals['_OAUTHCONNECTION']._serialized_start=10837 - _globals['_OAUTHCONNECTION']._serialized_end=10972 - _globals['_GETOAUTHCONNECTIONSTATUSREQUEST']._serialized_start=10974 - _globals['_GETOAUTHCONNECTIONSTATUSREQUEST']._serialized_end=11051 - _globals['_GETOAUTHCONNECTIONSTATUSRESPONSE']._serialized_start=11053 - _globals['_GETOAUTHCONNECTIONSTATUSRESPONSE']._serialized_end=11134 - _globals['_DISCONNECTOAUTHREQUEST']._serialized_start=11136 - _globals['_DISCONNECTOAUTHREQUEST']._serialized_end=11204 - _globals['_DISCONNECTOAUTHRESPONSE']._serialized_start=11206 - _globals['_DISCONNECTOAUTHRESPONSE']._serialized_end=11271 - _globals['_OAUTHCLIENTCONFIG']._serialized_start=11274 - _globals['_OAUTHCLIENTCONFIG']._serialized_end=11449 - _globals['_GETOAUTHCLIENTCONFIGREQUEST']._serialized_start=11451 - _globals['_GETOAUTHCLIENTCONFIGREQUEST']._serialized_end=11546 - _globals['_GETOAUTHCLIENTCONFIGRESPONSE']._serialized_start=11548 - _globals['_GETOAUTHCLIENTCONFIGRESPONSE']._serialized_end=11623 - _globals['_SETOAUTHCLIENTCONFIGREQUEST']._serialized_start=11626 - _globals['_SETOAUTHCLIENTCONFIGREQUEST']._serialized_end=11766 - _globals['_SETOAUTHCLIENTCONFIGRESPONSE']._serialized_start=11768 - _globals['_SETOAUTHCLIENTCONFIGRESPONSE']._serialized_end=11815 - _globals['_REGISTERWEBHOOKREQUEST']._serialized_start=11818 - _globals['_REGISTERWEBHOOKREQUEST']._serialized_end=11964 - _globals['_WEBHOOKCONFIGPROTO']._serialized_start=11967 - _globals['_WEBHOOKCONFIGPROTO']._serialized_end=12162 - _globals['_LISTWEBHOOKSREQUEST']._serialized_start=12164 - _globals['_LISTWEBHOOKSREQUEST']._serialized_end=12207 - _globals['_LISTWEBHOOKSRESPONSE']._serialized_start=12209 - _globals['_LISTWEBHOOKSRESPONSE']._serialized_end=12300 - _globals['_UPDATEWEBHOOKREQUEST']._serialized_start=12303 - _globals['_UPDATEWEBHOOKREQUEST']._serialized_end=12563 - _globals['_DELETEWEBHOOKREQUEST']._serialized_start=12565 - _globals['_DELETEWEBHOOKREQUEST']._serialized_end=12607 - _globals['_DELETEWEBHOOKRESPONSE']._serialized_start=12609 - _globals['_DELETEWEBHOOKRESPONSE']._serialized_end=12649 - _globals['_WEBHOOKDELIVERYPROTO']._serialized_start=12652 - _globals['_WEBHOOKDELIVERYPROTO']._serialized_end=12855 - _globals['_GETWEBHOOKDELIVERIESREQUEST']._serialized_start=12857 - _globals['_GETWEBHOOKDELIVERIESREQUEST']._serialized_end=12921 - _globals['_GETWEBHOOKDELIVERIESRESPONSE']._serialized_start=12923 - _globals['_GETWEBHOOKDELIVERIESRESPONSE']._serialized_end=13026 - _globals['_GRANTCLOUDCONSENTREQUEST']._serialized_start=13028 - _globals['_GRANTCLOUDCONSENTREQUEST']._serialized_end=13054 - _globals['_GRANTCLOUDCONSENTRESPONSE']._serialized_start=13056 - _globals['_GRANTCLOUDCONSENTRESPONSE']._serialized_end=13083 - _globals['_REVOKECLOUDCONSENTREQUEST']._serialized_start=13085 - _globals['_REVOKECLOUDCONSENTREQUEST']._serialized_end=13112 - _globals['_REVOKECLOUDCONSENTRESPONSE']._serialized_start=13114 - _globals['_REVOKECLOUDCONSENTRESPONSE']._serialized_end=13142 - _globals['_GETCLOUDCONSENTSTATUSREQUEST']._serialized_start=13144 - _globals['_GETCLOUDCONSENTSTATUSREQUEST']._serialized_end=13174 - _globals['_GETCLOUDCONSENTSTATUSRESPONSE']._serialized_start=13176 - _globals['_GETCLOUDCONSENTSTATUSRESPONSE']._serialized_end=13232 - _globals['_SETHUGGINGFACETOKENREQUEST']._serialized_start=13234 - _globals['_SETHUGGINGFACETOKENREQUEST']._serialized_end=13295 - _globals['_SETHUGGINGFACETOKENRESPONSE']._serialized_start=13297 - _globals['_SETHUGGINGFACETOKENRESPONSE']._serialized_end=13417 - _globals['_GETHUGGINGFACETOKENSTATUSREQUEST']._serialized_start=13419 - _globals['_GETHUGGINGFACETOKENSTATUSREQUEST']._serialized_end=13453 - _globals['_GETHUGGINGFACETOKENSTATUSRESPONSE']._serialized_start=13455 - _globals['_GETHUGGINGFACETOKENSTATUSRESPONSE']._serialized_end=13575 - _globals['_DELETEHUGGINGFACETOKENREQUEST']._serialized_start=13577 - _globals['_DELETEHUGGINGFACETOKENREQUEST']._serialized_end=13608 - _globals['_DELETEHUGGINGFACETOKENRESPONSE']._serialized_start=13610 - _globals['_DELETEHUGGINGFACETOKENRESPONSE']._serialized_end=13659 - _globals['_VALIDATEHUGGINGFACETOKENREQUEST']._serialized_start=13661 - _globals['_VALIDATEHUGGINGFACETOKENREQUEST']._serialized_end=13694 - _globals['_VALIDATEHUGGINGFACETOKENRESPONSE']._serialized_start=13696 - _globals['_VALIDATEHUGGINGFACETOKENRESPONSE']._serialized_end=13786 - _globals['_GETPREFERENCESREQUEST']._serialized_start=13788 - _globals['_GETPREFERENCESREQUEST']._serialized_end=13825 - _globals['_GETPREFERENCESRESPONSE']._serialized_start=13828 - _globals['_GETPREFERENCESRESPONSE']._serialized_end=14010 - _globals['_GETPREFERENCESRESPONSE_PREFERENCESENTRY']._serialized_start=13960 - _globals['_GETPREFERENCESRESPONSE_PREFERENCESENTRY']._serialized_end=14010 - _globals['_SETPREFERENCESREQUEST']._serialized_start=14013 - _globals['_SETPREFERENCESREQUEST']._serialized_end=14219 - _globals['_SETPREFERENCESREQUEST_PREFERENCESENTRY']._serialized_start=13960 - _globals['_SETPREFERENCESREQUEST_PREFERENCESENTRY']._serialized_end=14010 - _globals['_SETPREFERENCESRESPONSE']._serialized_start=14222 - _globals['_SETPREFERENCESRESPONSE']._serialized_end=14491 - _globals['_SETPREFERENCESRESPONSE_SERVERPREFERENCESENTRY']._serialized_start=14435 - _globals['_SETPREFERENCESRESPONSE_SERVERPREFERENCESENTRY']._serialized_end=14491 - _globals['_STARTINTEGRATIONSYNCREQUEST']._serialized_start=14493 - _globals['_STARTINTEGRATIONSYNCREQUEST']._serialized_end=14546 - _globals['_STARTINTEGRATIONSYNCRESPONSE']._serialized_start=14548 - _globals['_STARTINTEGRATIONSYNCRESPONSE']._serialized_end=14615 - _globals['_GETSYNCSTATUSREQUEST']._serialized_start=14617 - _globals['_GETSYNCSTATUSREQUEST']._serialized_end=14660 - _globals['_GETSYNCSTATUSRESPONSE']._serialized_start=14663 - _globals['_GETSYNCSTATUSRESPONSE']._serialized_end=14903 - _globals['_LISTSYNCHISTORYREQUEST']._serialized_start=14905 - _globals['_LISTSYNCHISTORYREQUEST']._serialized_end=14984 - _globals['_LISTSYNCHISTORYRESPONSE']._serialized_start=14986 - _globals['_LISTSYNCHISTORYRESPONSE']._serialized_end=15070 - _globals['_SYNCRUNPROTO']._serialized_start=15073 - _globals['_SYNCRUNPROTO']._serialized_end=15269 - _globals['_GETUSERINTEGRATIONSREQUEST']._serialized_start=15271 - _globals['_GETUSERINTEGRATIONSREQUEST']._serialized_end=15299 - _globals['_INTEGRATIONINFO']._serialized_start=15301 - _globals['_INTEGRATIONINFO']._serialized_end=15396 - _globals['_GETUSERINTEGRATIONSRESPONSE']._serialized_start=15398 - _globals['_GETUSERINTEGRATIONSRESPONSE']._serialized_end=15476 - _globals['_GETRECENTLOGSREQUEST']._serialized_start=15478 - _globals['_GETRECENTLOGSREQUEST']._serialized_end=15546 - _globals['_GETRECENTLOGSRESPONSE']._serialized_start=15548 - _globals['_GETRECENTLOGSRESPONSE']._serialized_end=15610 - _globals['_LOGENTRYPROTO']._serialized_start=15613 - _globals['_LOGENTRYPROTO']._serialized_end=15894 - _globals['_LOGENTRYPROTO_DETAILSENTRY']._serialized_start=15848 - _globals['_LOGENTRYPROTO_DETAILSENTRY']._serialized_end=15894 - _globals['_GETPERFORMANCEMETRICSREQUEST']._serialized_start=15896 - _globals['_GETPERFORMANCEMETRICSREQUEST']._serialized_end=15949 - _globals['_GETPERFORMANCEMETRICSRESPONSE']._serialized_start=15952 - _globals['_GETPERFORMANCEMETRICSRESPONSE']._serialized_end=16087 - _globals['_PERFORMANCEMETRICSPOINT']._serialized_start=16090 - _globals['_PERFORMANCEMETRICSPOINT']._serialized_end=16331 - _globals['_CLAIMMAPPINGPROTO']._serialized_start=16334 - _globals['_CLAIMMAPPINGPROTO']._serialized_end=16670 - _globals['_OIDCDISCOVERYPROTO']._serialized_start=16673 - _globals['_OIDCDISCOVERYPROTO']._serialized_end=17048 - _globals['_OIDCPROVIDERPROTO']._serialized_start=17051 - _globals['_OIDCPROVIDERPROTO']._serialized_end=17504 - _globals['_REGISTEROIDCPROVIDERREQUEST']._serialized_start=17507 - _globals['_REGISTEROIDCPROVIDERREQUEST']._serialized_end=17875 - _globals['_LISTOIDCPROVIDERSREQUEST']._serialized_start=17877 - _globals['_LISTOIDCPROVIDERSREQUEST']._serialized_end=17969 - _globals['_LISTOIDCPROVIDERSRESPONSE']._serialized_start=17971 - _globals['_LISTOIDCPROVIDERSRESPONSE']._serialized_end=18067 - _globals['_GETOIDCPROVIDERREQUEST']._serialized_start=18069 - _globals['_GETOIDCPROVIDERREQUEST']._serialized_end=18114 - _globals['_UPDATEOIDCPROVIDERREQUEST']._serialized_start=18117 - _globals['_UPDATEOIDCPROVIDERREQUEST']._serialized_end=18406 - _globals['_DELETEOIDCPROVIDERREQUEST']._serialized_start=18408 - _globals['_DELETEOIDCPROVIDERREQUEST']._serialized_end=18456 - _globals['_DELETEOIDCPROVIDERRESPONSE']._serialized_start=18458 - _globals['_DELETEOIDCPROVIDERRESPONSE']._serialized_end=18503 - _globals['_REFRESHOIDCDISCOVERYREQUEST']._serialized_start=18505 - _globals['_REFRESHOIDCDISCOVERYREQUEST']._serialized_end=18620 - _globals['_REFRESHOIDCDISCOVERYRESPONSE']._serialized_start=18623 - _globals['_REFRESHOIDCDISCOVERYRESPONSE']._serialized_end=18817 - _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._serialized_start=18771 - _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._serialized_end=18817 - _globals['_LISTOIDCPRESETSREQUEST']._serialized_start=18819 - _globals['_LISTOIDCPRESETSREQUEST']._serialized_end=18843 - _globals['_OIDCPRESETPROTO']._serialized_start=18846 - _globals['_OIDCPRESETPROTO']._serialized_end=19030 - _globals['_LISTOIDCPRESETSRESPONSE']._serialized_start=19032 - _globals['_LISTOIDCPRESETSRESPONSE']._serialized_end=19101 - _globals['_EXPORTRULESPROTO']._serialized_start=19104 - _globals['_EXPORTRULESPROTO']._serialized_end=19338 - _globals['_TRIGGERRULESPROTO']._serialized_start=19341 - _globals['_TRIGGERRULESPROTO']._serialized_end=19477 - _globals['_WORKSPACESETTINGSPROTO']._serialized_start=19480 - _globals['_WORKSPACESETTINGSPROTO']._serialized_end=19773 - _globals['_PROJECTSETTINGSPROTO']._serialized_start=19776 - _globals['_PROJECTSETTINGSPROTO']._serialized_end=20067 - _globals['_PROJECTPROTO']._serialized_start=20070 - _globals['_PROJECTPROTO']._serialized_end=20393 - _globals['_PROJECTMEMBERSHIPPROTO']._serialized_start=20395 - _globals['_PROJECTMEMBERSHIPPROTO']._serialized_end=20517 - _globals['_CREATEPROJECTREQUEST']._serialized_start=20520 - _globals['_CREATEPROJECTREQUEST']._serialized_end=20716 - _globals['_GETPROJECTREQUEST']._serialized_start=20718 - _globals['_GETPROJECTREQUEST']._serialized_end=20757 - _globals['_GETPROJECTBYSLUGREQUEST']._serialized_start=20759 - _globals['_GETPROJECTBYSLUGREQUEST']._serialized_end=20820 - _globals['_LISTPROJECTSREQUEST']._serialized_start=20822 - _globals['_LISTPROJECTSREQUEST']._serialized_end=20922 - _globals['_LISTPROJECTSRESPONSE']._serialized_start=20924 - _globals['_LISTPROJECTSRESPONSE']._serialized_end=21009 - _globals['_UPDATEPROJECTREQUEST']._serialized_start=21012 - _globals['_UPDATEPROJECTREQUEST']._serialized_end=21220 - _globals['_ARCHIVEPROJECTREQUEST']._serialized_start=21222 - _globals['_ARCHIVEPROJECTREQUEST']._serialized_end=21265 - _globals['_RESTOREPROJECTREQUEST']._serialized_start=21267 - _globals['_RESTOREPROJECTREQUEST']._serialized_end=21310 - _globals['_DELETEPROJECTREQUEST']._serialized_start=21312 - _globals['_DELETEPROJECTREQUEST']._serialized_end=21354 - _globals['_DELETEPROJECTRESPONSE']._serialized_start=21356 - _globals['_DELETEPROJECTRESPONSE']._serialized_end=21396 - _globals['_SETACTIVEPROJECTREQUEST']._serialized_start=21398 - _globals['_SETACTIVEPROJECTREQUEST']._serialized_end=21465 - _globals['_SETACTIVEPROJECTRESPONSE']._serialized_start=21467 - _globals['_SETACTIVEPROJECTRESPONSE']._serialized_end=21493 - _globals['_GETACTIVEPROJECTREQUEST']._serialized_start=21495 - _globals['_GETACTIVEPROJECTREQUEST']._serialized_end=21542 - _globals['_GETACTIVEPROJECTRESPONSE']._serialized_start=21544 - _globals['_GETACTIVEPROJECTRESPONSE']._serialized_end=21651 - _globals['_ADDPROJECTMEMBERREQUEST']._serialized_start=21653 - _globals['_ADDPROJECTMEMBERREQUEST']._serialized_end=21757 - _globals['_UPDATEPROJECTMEMBERROLEREQUEST']._serialized_start=21759 - _globals['_UPDATEPROJECTMEMBERROLEREQUEST']._serialized_end=21870 - _globals['_REMOVEPROJECTMEMBERREQUEST']._serialized_start=21872 - _globals['_REMOVEPROJECTMEMBERREQUEST']._serialized_end=21937 - _globals['_REMOVEPROJECTMEMBERRESPONSE']._serialized_start=21939 - _globals['_REMOVEPROJECTMEMBERRESPONSE']._serialized_end=21985 - _globals['_LISTPROJECTMEMBERSREQUEST']._serialized_start=21987 - _globals['_LISTPROJECTMEMBERSREQUEST']._serialized_end=22065 - _globals['_LISTPROJECTMEMBERSRESPONSE']._serialized_start=22067 - _globals['_LISTPROJECTMEMBERSRESPONSE']._serialized_end=22167 - _globals['_GETCURRENTUSERREQUEST']._serialized_start=22169 - _globals['_GETCURRENTUSERREQUEST']._serialized_end=22192 - _globals['_GETCURRENTUSERRESPONSE']._serialized_start=22195 - _globals['_GETCURRENTUSERRESPONSE']._serialized_end=22382 - _globals['_WORKSPACEPROTO']._serialized_start=22384 - _globals['_WORKSPACEPROTO']._serialized_end=22474 - _globals['_LISTWORKSPACESREQUEST']._serialized_start=22476 - _globals['_LISTWORKSPACESREQUEST']._serialized_end=22530 - _globals['_LISTWORKSPACESRESPONSE']._serialized_start=22532 - _globals['_LISTWORKSPACESRESPONSE']._serialized_end=22623 - _globals['_SWITCHWORKSPACEREQUEST']._serialized_start=22625 - _globals['_SWITCHWORKSPACEREQUEST']._serialized_end=22671 - _globals['_SWITCHWORKSPACERESPONSE']._serialized_start=22673 - _globals['_SWITCHWORKSPACERESPONSE']._serialized_end=22783 - _globals['_GETWORKSPACESETTINGSREQUEST']._serialized_start=22785 - _globals['_GETWORKSPACESETTINGSREQUEST']._serialized_end=22836 - _globals['_UPDATEWORKSPACESETTINGSREQUEST']._serialized_start=22838 - _globals['_UPDATEWORKSPACESETTINGSREQUEST']._serialized_end=22944 - _globals['_TASKPROTO']._serialized_start=22947 - _globals['_TASKPROTO']._serialized_end=23197 - _globals['_TASKWITHMEETINGPROTO']._serialized_start=23200 - _globals['_TASKWITHMEETINGPROTO']._serialized_end=23328 - _globals['_LISTTASKSREQUEST']._serialized_start=23331 - _globals['_LISTTASKSREQUEST']._serialized_end=23526 - _globals['_LISTTASKSRESPONSE']._serialized_start=23528 - _globals['_LISTTASKSRESPONSE']._serialized_end=23615 - _globals['_UPDATETASKREQUEST']._serialized_start=23618 - _globals['_UPDATETASKREQUEST']._serialized_end=23775 - _globals['_UPDATETASKRESPONSE']._serialized_start=23777 - _globals['_UPDATETASKRESPONSE']._serialized_end=23832 - _globals['_GETANALYTICSOVERVIEWREQUEST']._serialized_start=23835 - _globals['_GETANALYTICSOVERVIEWREQUEST']._serialized_end=23963 - _globals['_DAILYMEETINGSTATSPROTO']._serialized_start=23965 - _globals['_DAILYMEETINGSTATSPROTO']._serialized_end=24065 - _globals['_GETANALYTICSOVERVIEWRESPONSE']._serialized_start=24068 - _globals['_GETANALYTICSOVERVIEWRESPONSE']._serialized_end=24263 - _globals['_SPEAKERSTATPROTO']._serialized_start=24266 - _globals['_SPEAKERSTATPROTO']._serialized_end=24416 - _globals['_LISTSPEAKERSTATSREQUEST']._serialized_start=24418 - _globals['_LISTSPEAKERSTATSREQUEST']._serialized_end=24542 - _globals['_LISTSPEAKERSTATSRESPONSE']._serialized_start=24544 - _globals['_LISTSPEAKERSTATSRESPONSE']._serialized_end=24616 - _globals['_NOTEFLOWSERVICE']._serialized_start=26472 - _globals['_NOTEFLOWSERVICE']._serialized_end=35044 + _globals['_LISTMEETINGSREQUEST']._serialized_end=1790 + _globals['_LISTMEETINGSRESPONSE']._serialized_start=1792 + _globals['_LISTMEETINGSRESPONSE']._serialized_end=1872 + _globals['_GETMEETINGREQUEST']._serialized_start=1874 + _globals['_GETMEETINGREQUEST']._serialized_end=1964 + _globals['_DELETEMEETINGREQUEST']._serialized_start=1966 + _globals['_DELETEMEETINGREQUEST']._serialized_end=2008 + _globals['_DELETEMEETINGRESPONSE']._serialized_start=2010 + _globals['_DELETEMEETINGRESPONSE']._serialized_end=2050 + _globals['_SUMMARY']._serialized_start=2053 + _globals['_SUMMARY']._serialized_end=2238 + _globals['_KEYPOINT']._serialized_start=2240 + _globals['_KEYPOINT']._serialized_end=2323 + _globals['_ACTIONITEM']._serialized_start=2325 + _globals['_ACTIONITEM']._serialized_end=2446 + _globals['_SUMMARIZATIONOPTIONS']._serialized_start=2448 + _globals['_SUMMARIZATIONOPTIONS']._serialized_end=2540 + _globals['_GENERATESUMMARYREQUEST']._serialized_start=2542 + _globals['_GENERATESUMMARYREQUEST']._serialized_end=2661 + _globals['_SUMMARIZATIONTEMPLATEPROTO']._serialized_start=2664 + _globals['_SUMMARIZATIONTEMPLATEPROTO']._serialized_end=3020 + _globals['_SUMMARIZATIONTEMPLATEVERSIONPROTO']._serialized_start=3023 + _globals['_SUMMARIZATIONTEMPLATEVERSIONPROTO']._serialized_end=3234 + _globals['_LISTSUMMARIZATIONTEMPLATESREQUEST']._serialized_start=3237 + _globals['_LISTSUMMARIZATIONTEMPLATESREQUEST']._serialized_end=3375 + _globals['_LISTSUMMARIZATIONTEMPLATESRESPONSE']._serialized_start=3377 + _globals['_LISTSUMMARIZATIONTEMPLATESRESPONSE']._serialized_end=3491 + _globals['_GETSUMMARIZATIONTEMPLATEREQUEST']._serialized_start=3493 + _globals['_GETSUMMARIZATIONTEMPLATEREQUEST']._serialized_end=3580 + _globals['_GETSUMMARIZATIONTEMPLATERESPONSE']._serialized_start=3583 + _globals['_GETSUMMARIZATIONTEMPLATERESPONSE']._serialized_end=3768 + _globals['_SUMMARIZATIONTEMPLATEMUTATIONRESPONSE']._serialized_start=3771 + _globals['_SUMMARIZATIONTEMPLATEMUTATIONRESPONSE']._serialized_end=3945 + _globals['_CREATESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=3948 + _globals['_CREATESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4121 + _globals['_UPDATESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=4124 + _globals['_UPDATESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4327 + _globals['_ARCHIVESUMMARIZATIONTEMPLATEREQUEST']._serialized_start=4329 + _globals['_ARCHIVESUMMARIZATIONTEMPLATEREQUEST']._serialized_end=4387 + _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSREQUEST']._serialized_start=4389 + _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSREQUEST']._serialized_end=4483 + _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSRESPONSE']._serialized_start=4485 + _globals['_LISTSUMMARIZATIONTEMPLATEVERSIONSRESPONSE']._serialized_end=4612 + _globals['_RESTORESUMMARIZATIONTEMPLATEVERSIONREQUEST']._serialized_start=4614 + _globals['_RESTORESUMMARIZATIONTEMPLATEVERSIONREQUEST']._serialized_end=4699 + _globals['_SERVERINFOREQUEST']._serialized_start=4701 + _globals['_SERVERINFOREQUEST']._serialized_end=4720 + _globals['_SERVERINFO']._serialized_start=4723 + _globals['_SERVERINFO']._serialized_end=5238 + _globals['_ASRCONFIGURATION']._serialized_start=5241 + _globals['_ASRCONFIGURATION']._serialized_end=5541 + _globals['_GETASRCONFIGURATIONREQUEST']._serialized_start=5543 + _globals['_GETASRCONFIGURATIONREQUEST']._serialized_end=5571 + _globals['_GETASRCONFIGURATIONRESPONSE']._serialized_start=5573 + _globals['_GETASRCONFIGURATIONRESPONSE']._serialized_end=5653 + _globals['_UPDATEASRCONFIGURATIONREQUEST']._serialized_start=5656 + _globals['_UPDATEASRCONFIGURATIONREQUEST']._serialized_end=5850 + _globals['_UPDATEASRCONFIGURATIONRESPONSE']._serialized_start=5852 + _globals['_UPDATEASRCONFIGURATIONRESPONSE']._serialized_end=5978 + _globals['_GETASRCONFIGURATIONJOBSTATUSREQUEST']._serialized_start=5980 + _globals['_GETASRCONFIGURATIONJOBSTATUSREQUEST']._serialized_end=6033 + _globals['_ASRCONFIGURATIONJOBSTATUS']._serialized_start=6036 + _globals['_ASRCONFIGURATIONJOBSTATUS']._serialized_end=6262 + _globals['_STREAMINGCONFIGURATION']._serialized_start=6265 + _globals['_STREAMINGCONFIGURATION']._serialized_end=6498 + _globals['_GETSTREAMINGCONFIGURATIONREQUEST']._serialized_start=6500 + _globals['_GETSTREAMINGCONFIGURATIONREQUEST']._serialized_end=6534 + _globals['_GETSTREAMINGCONFIGURATIONRESPONSE']._serialized_start=6536 + _globals['_GETSTREAMINGCONFIGURATIONRESPONSE']._serialized_end=6628 + _globals['_UPDATESTREAMINGCONFIGURATIONREQUEST']._serialized_start=6631 + _globals['_UPDATESTREAMINGCONFIGURATIONREQUEST']._serialized_end=7086 + _globals['_UPDATESTREAMINGCONFIGURATIONRESPONSE']._serialized_start=7088 + _globals['_UPDATESTREAMINGCONFIGURATIONRESPONSE']._serialized_end=7183 + _globals['_ANNOTATION']._serialized_start=7186 + _globals['_ANNOTATION']._serialized_end=7374 + _globals['_ADDANNOTATIONREQUEST']._serialized_start=7377 + _globals['_ADDANNOTATIONREQUEST']._serialized_end=7543 + _globals['_GETANNOTATIONREQUEST']._serialized_start=7545 + _globals['_GETANNOTATIONREQUEST']._serialized_end=7590 + _globals['_LISTANNOTATIONSREQUEST']._serialized_start=7592 + _globals['_LISTANNOTATIONSREQUEST']._serialized_end=7674 + _globals['_LISTANNOTATIONSRESPONSE']._serialized_start=7676 + _globals['_LISTANNOTATIONSRESPONSE']._serialized_end=7744 + _globals['_UPDATEANNOTATIONREQUEST']._serialized_start=7747 + _globals['_UPDATEANNOTATIONREQUEST']._serialized_end=7919 + _globals['_DELETEANNOTATIONREQUEST']._serialized_start=7921 + _globals['_DELETEANNOTATIONREQUEST']._serialized_end=7969 + _globals['_DELETEANNOTATIONRESPONSE']._serialized_start=7971 + _globals['_DELETEANNOTATIONRESPONSE']._serialized_end=8014 + _globals['_PROCESSINGSTEPSTATE']._serialized_start=8017 + _globals['_PROCESSINGSTEPSTATE']._serialized_end=8151 + _globals['_PROCESSINGSTATUS']._serialized_start=8154 + _globals['_PROCESSINGSTATUS']._serialized_end=8321 + _globals['_EXPORTTRANSCRIPTREQUEST']._serialized_start=8323 + _globals['_EXPORTTRANSCRIPTREQUEST']._serialized_end=8408 + _globals['_EXPORTTRANSCRIPTRESPONSE']._serialized_start=8410 + _globals['_EXPORTTRANSCRIPTRESPONSE']._serialized_end=8498 + _globals['_REFINESPEAKERDIARIZATIONREQUEST']._serialized_start=8500 + _globals['_REFINESPEAKERDIARIZATIONREQUEST']._serialized_end=8575 + _globals['_REFINESPEAKERDIARIZATIONRESPONSE']._serialized_start=8578 + _globals['_REFINESPEAKERDIARIZATIONRESPONSE']._serialized_end=8735 + _globals['_RENAMESPEAKERREQUEST']._serialized_start=8737 + _globals['_RENAMESPEAKERREQUEST']._serialized_end=8829 + _globals['_RENAMESPEAKERRESPONSE']._serialized_start=8831 + _globals['_RENAMESPEAKERRESPONSE']._serialized_end=8897 + _globals['_GETDIARIZATIONJOBSTATUSREQUEST']._serialized_start=8899 + _globals['_GETDIARIZATIONJOBSTATUSREQUEST']._serialized_end=8947 + _globals['_DIARIZATIONJOBSTATUS']._serialized_start=8950 + _globals['_DIARIZATIONJOBSTATUS']._serialized_end=9121 + _globals['_CANCELDIARIZATIONJOBREQUEST']._serialized_start=9123 + _globals['_CANCELDIARIZATIONJOBREQUEST']._serialized_end=9168 + _globals['_CANCELDIARIZATIONJOBRESPONSE']._serialized_start=9170 + _globals['_CANCELDIARIZATIONJOBRESPONSE']._serialized_end=9277 + _globals['_GETACTIVEDIARIZATIONJOBSREQUEST']._serialized_start=9279 + _globals['_GETACTIVEDIARIZATIONJOBSREQUEST']._serialized_end=9312 + _globals['_GETACTIVEDIARIZATIONJOBSRESPONSE']._serialized_start=9314 + _globals['_GETACTIVEDIARIZATIONJOBSRESPONSE']._serialized_end=9394 + _globals['_EXTRACTENTITIESREQUEST']._serialized_start=9396 + _globals['_EXTRACTENTITIESREQUEST']._serialized_end=9463 + _globals['_EXTRACTEDENTITY']._serialized_start=9465 + _globals['_EXTRACTEDENTITY']._serialized_end=9586 + _globals['_EXTRACTENTITIESRESPONSE']._serialized_start=9588 + _globals['_EXTRACTENTITIESRESPONSE']._serialized_end=9695 + _globals['_UPDATEENTITYREQUEST']._serialized_start=9697 + _globals['_UPDATEENTITYREQUEST']._serialized_end=9789 + _globals['_UPDATEENTITYRESPONSE']._serialized_start=9791 + _globals['_UPDATEENTITYRESPONSE']._serialized_end=9856 + _globals['_DELETEENTITYREQUEST']._serialized_start=9858 + _globals['_DELETEENTITYREQUEST']._serialized_end=9918 + _globals['_DELETEENTITYRESPONSE']._serialized_start=9920 + _globals['_DELETEENTITYRESPONSE']._serialized_end=9959 + _globals['_CALENDAREVENT']._serialized_start=9962 + _globals['_CALENDAREVENT']._serialized_end=10161 + _globals['_LISTCALENDAREVENTSREQUEST']._serialized_start=10163 + _globals['_LISTCALENDAREVENTSREQUEST']._serialized_end=10244 + _globals['_LISTCALENDAREVENTSRESPONSE']._serialized_start=10246 + _globals['_LISTCALENDAREVENTSRESPONSE']._serialized_end=10336 + _globals['_GETCALENDARPROVIDERSREQUEST']._serialized_start=10338 + _globals['_GETCALENDARPROVIDERSREQUEST']._serialized_end=10367 + _globals['_CALENDARPROVIDER']._serialized_start=10369 + _globals['_CALENDARPROVIDER']._serialized_end=10449 + _globals['_GETCALENDARPROVIDERSRESPONSE']._serialized_start=10451 + _globals['_GETCALENDARPROVIDERSRESPONSE']._serialized_end=10528 + _globals['_INITIATEOAUTHREQUEST']._serialized_start=10530 + _globals['_INITIATEOAUTHREQUEST']._serialized_end=10618 + _globals['_INITIATEOAUTHRESPONSE']._serialized_start=10620 + _globals['_INITIATEOAUTHRESPONSE']._serialized_end=10676 + _globals['_COMPLETEOAUTHREQUEST']._serialized_start=10678 + _globals['_COMPLETEOAUTHREQUEST']._serialized_end=10747 + _globals['_COMPLETEOAUTHRESPONSE']._serialized_start=10749 + _globals['_COMPLETEOAUTHRESPONSE']._serialized_end=10860 + _globals['_OAUTHCONNECTION']._serialized_start=10863 + _globals['_OAUTHCONNECTION']._serialized_end=10998 + _globals['_GETOAUTHCONNECTIONSTATUSREQUEST']._serialized_start=11000 + _globals['_GETOAUTHCONNECTIONSTATUSREQUEST']._serialized_end=11077 + _globals['_GETOAUTHCONNECTIONSTATUSRESPONSE']._serialized_start=11079 + _globals['_GETOAUTHCONNECTIONSTATUSRESPONSE']._serialized_end=11160 + _globals['_DISCONNECTOAUTHREQUEST']._serialized_start=11162 + _globals['_DISCONNECTOAUTHREQUEST']._serialized_end=11230 + _globals['_DISCONNECTOAUTHRESPONSE']._serialized_start=11232 + _globals['_DISCONNECTOAUTHRESPONSE']._serialized_end=11297 + _globals['_OAUTHCLIENTCONFIG']._serialized_start=11300 + _globals['_OAUTHCLIENTCONFIG']._serialized_end=11475 + _globals['_GETOAUTHCLIENTCONFIGREQUEST']._serialized_start=11477 + _globals['_GETOAUTHCLIENTCONFIGREQUEST']._serialized_end=11572 + _globals['_GETOAUTHCLIENTCONFIGRESPONSE']._serialized_start=11574 + _globals['_GETOAUTHCLIENTCONFIGRESPONSE']._serialized_end=11649 + _globals['_SETOAUTHCLIENTCONFIGREQUEST']._serialized_start=11652 + _globals['_SETOAUTHCLIENTCONFIGREQUEST']._serialized_end=11792 + _globals['_SETOAUTHCLIENTCONFIGRESPONSE']._serialized_start=11794 + _globals['_SETOAUTHCLIENTCONFIGRESPONSE']._serialized_end=11841 + _globals['_REGISTERWEBHOOKREQUEST']._serialized_start=11844 + _globals['_REGISTERWEBHOOKREQUEST']._serialized_end=11990 + _globals['_WEBHOOKCONFIGPROTO']._serialized_start=11993 + _globals['_WEBHOOKCONFIGPROTO']._serialized_end=12188 + _globals['_LISTWEBHOOKSREQUEST']._serialized_start=12190 + _globals['_LISTWEBHOOKSREQUEST']._serialized_end=12233 + _globals['_LISTWEBHOOKSRESPONSE']._serialized_start=12235 + _globals['_LISTWEBHOOKSRESPONSE']._serialized_end=12326 + _globals['_UPDATEWEBHOOKREQUEST']._serialized_start=12329 + _globals['_UPDATEWEBHOOKREQUEST']._serialized_end=12589 + _globals['_DELETEWEBHOOKREQUEST']._serialized_start=12591 + _globals['_DELETEWEBHOOKREQUEST']._serialized_end=12633 + _globals['_DELETEWEBHOOKRESPONSE']._serialized_start=12635 + _globals['_DELETEWEBHOOKRESPONSE']._serialized_end=12675 + _globals['_WEBHOOKDELIVERYPROTO']._serialized_start=12678 + _globals['_WEBHOOKDELIVERYPROTO']._serialized_end=12881 + _globals['_GETWEBHOOKDELIVERIESREQUEST']._serialized_start=12883 + _globals['_GETWEBHOOKDELIVERIESREQUEST']._serialized_end=12947 + _globals['_GETWEBHOOKDELIVERIESRESPONSE']._serialized_start=12949 + _globals['_GETWEBHOOKDELIVERIESRESPONSE']._serialized_end=13052 + _globals['_GRANTCLOUDCONSENTREQUEST']._serialized_start=13054 + _globals['_GRANTCLOUDCONSENTREQUEST']._serialized_end=13080 + _globals['_GRANTCLOUDCONSENTRESPONSE']._serialized_start=13082 + _globals['_GRANTCLOUDCONSENTRESPONSE']._serialized_end=13109 + _globals['_REVOKECLOUDCONSENTREQUEST']._serialized_start=13111 + _globals['_REVOKECLOUDCONSENTREQUEST']._serialized_end=13138 + _globals['_REVOKECLOUDCONSENTRESPONSE']._serialized_start=13140 + _globals['_REVOKECLOUDCONSENTRESPONSE']._serialized_end=13168 + _globals['_GETCLOUDCONSENTSTATUSREQUEST']._serialized_start=13170 + _globals['_GETCLOUDCONSENTSTATUSREQUEST']._serialized_end=13200 + _globals['_GETCLOUDCONSENTSTATUSRESPONSE']._serialized_start=13202 + _globals['_GETCLOUDCONSENTSTATUSRESPONSE']._serialized_end=13258 + _globals['_SETHUGGINGFACETOKENREQUEST']._serialized_start=13260 + _globals['_SETHUGGINGFACETOKENREQUEST']._serialized_end=13321 + _globals['_SETHUGGINGFACETOKENRESPONSE']._serialized_start=13323 + _globals['_SETHUGGINGFACETOKENRESPONSE']._serialized_end=13443 + _globals['_GETHUGGINGFACETOKENSTATUSREQUEST']._serialized_start=13445 + _globals['_GETHUGGINGFACETOKENSTATUSREQUEST']._serialized_end=13479 + _globals['_GETHUGGINGFACETOKENSTATUSRESPONSE']._serialized_start=13481 + _globals['_GETHUGGINGFACETOKENSTATUSRESPONSE']._serialized_end=13601 + _globals['_DELETEHUGGINGFACETOKENREQUEST']._serialized_start=13603 + _globals['_DELETEHUGGINGFACETOKENREQUEST']._serialized_end=13634 + _globals['_DELETEHUGGINGFACETOKENRESPONSE']._serialized_start=13636 + _globals['_DELETEHUGGINGFACETOKENRESPONSE']._serialized_end=13685 + _globals['_VALIDATEHUGGINGFACETOKENREQUEST']._serialized_start=13687 + _globals['_VALIDATEHUGGINGFACETOKENREQUEST']._serialized_end=13720 + _globals['_VALIDATEHUGGINGFACETOKENRESPONSE']._serialized_start=13722 + _globals['_VALIDATEHUGGINGFACETOKENRESPONSE']._serialized_end=13812 + _globals['_GETPREFERENCESREQUEST']._serialized_start=13814 + _globals['_GETPREFERENCESREQUEST']._serialized_end=13851 + _globals['_GETPREFERENCESRESPONSE']._serialized_start=13854 + _globals['_GETPREFERENCESRESPONSE']._serialized_end=14036 + _globals['_GETPREFERENCESRESPONSE_PREFERENCESENTRY']._serialized_start=13986 + _globals['_GETPREFERENCESRESPONSE_PREFERENCESENTRY']._serialized_end=14036 + _globals['_SETPREFERENCESREQUEST']._serialized_start=14039 + _globals['_SETPREFERENCESREQUEST']._serialized_end=14245 + _globals['_SETPREFERENCESREQUEST_PREFERENCESENTRY']._serialized_start=13986 + _globals['_SETPREFERENCESREQUEST_PREFERENCESENTRY']._serialized_end=14036 + _globals['_SETPREFERENCESRESPONSE']._serialized_start=14248 + _globals['_SETPREFERENCESRESPONSE']._serialized_end=14517 + _globals['_SETPREFERENCESRESPONSE_SERVERPREFERENCESENTRY']._serialized_start=14461 + _globals['_SETPREFERENCESRESPONSE_SERVERPREFERENCESENTRY']._serialized_end=14517 + _globals['_STARTINTEGRATIONSYNCREQUEST']._serialized_start=14519 + _globals['_STARTINTEGRATIONSYNCREQUEST']._serialized_end=14572 + _globals['_STARTINTEGRATIONSYNCRESPONSE']._serialized_start=14574 + _globals['_STARTINTEGRATIONSYNCRESPONSE']._serialized_end=14641 + _globals['_GETSYNCSTATUSREQUEST']._serialized_start=14643 + _globals['_GETSYNCSTATUSREQUEST']._serialized_end=14686 + _globals['_GETSYNCSTATUSRESPONSE']._serialized_start=14689 + _globals['_GETSYNCSTATUSRESPONSE']._serialized_end=14929 + _globals['_LISTSYNCHISTORYREQUEST']._serialized_start=14931 + _globals['_LISTSYNCHISTORYREQUEST']._serialized_end=15010 + _globals['_LISTSYNCHISTORYRESPONSE']._serialized_start=15012 + _globals['_LISTSYNCHISTORYRESPONSE']._serialized_end=15096 + _globals['_SYNCRUNPROTO']._serialized_start=15099 + _globals['_SYNCRUNPROTO']._serialized_end=15295 + _globals['_GETUSERINTEGRATIONSREQUEST']._serialized_start=15297 + _globals['_GETUSERINTEGRATIONSREQUEST']._serialized_end=15325 + _globals['_INTEGRATIONINFO']._serialized_start=15327 + _globals['_INTEGRATIONINFO']._serialized_end=15422 + _globals['_GETUSERINTEGRATIONSRESPONSE']._serialized_start=15424 + _globals['_GETUSERINTEGRATIONSRESPONSE']._serialized_end=15502 + _globals['_GETRECENTLOGSREQUEST']._serialized_start=15504 + _globals['_GETRECENTLOGSREQUEST']._serialized_end=15572 + _globals['_GETRECENTLOGSRESPONSE']._serialized_start=15574 + _globals['_GETRECENTLOGSRESPONSE']._serialized_end=15636 + _globals['_LOGENTRYPROTO']._serialized_start=15639 + _globals['_LOGENTRYPROTO']._serialized_end=15920 + _globals['_LOGENTRYPROTO_DETAILSENTRY']._serialized_start=15874 + _globals['_LOGENTRYPROTO_DETAILSENTRY']._serialized_end=15920 + _globals['_GETPERFORMANCEMETRICSREQUEST']._serialized_start=15922 + _globals['_GETPERFORMANCEMETRICSREQUEST']._serialized_end=15975 + _globals['_GETPERFORMANCEMETRICSRESPONSE']._serialized_start=15978 + _globals['_GETPERFORMANCEMETRICSRESPONSE']._serialized_end=16113 + _globals['_PERFORMANCEMETRICSPOINT']._serialized_start=16116 + _globals['_PERFORMANCEMETRICSPOINT']._serialized_end=16357 + _globals['_CLAIMMAPPINGPROTO']._serialized_start=16360 + _globals['_CLAIMMAPPINGPROTO']._serialized_end=16696 + _globals['_OIDCDISCOVERYPROTO']._serialized_start=16699 + _globals['_OIDCDISCOVERYPROTO']._serialized_end=17074 + _globals['_OIDCPROVIDERPROTO']._serialized_start=17077 + _globals['_OIDCPROVIDERPROTO']._serialized_end=17530 + _globals['_REGISTEROIDCPROVIDERREQUEST']._serialized_start=17533 + _globals['_REGISTEROIDCPROVIDERREQUEST']._serialized_end=17901 + _globals['_LISTOIDCPROVIDERSREQUEST']._serialized_start=17903 + _globals['_LISTOIDCPROVIDERSREQUEST']._serialized_end=17995 + _globals['_LISTOIDCPROVIDERSRESPONSE']._serialized_start=17997 + _globals['_LISTOIDCPROVIDERSRESPONSE']._serialized_end=18093 + _globals['_GETOIDCPROVIDERREQUEST']._serialized_start=18095 + _globals['_GETOIDCPROVIDERREQUEST']._serialized_end=18140 + _globals['_UPDATEOIDCPROVIDERREQUEST']._serialized_start=18143 + _globals['_UPDATEOIDCPROVIDERREQUEST']._serialized_end=18432 + _globals['_DELETEOIDCPROVIDERREQUEST']._serialized_start=18434 + _globals['_DELETEOIDCPROVIDERREQUEST']._serialized_end=18482 + _globals['_DELETEOIDCPROVIDERRESPONSE']._serialized_start=18484 + _globals['_DELETEOIDCPROVIDERRESPONSE']._serialized_end=18529 + _globals['_REFRESHOIDCDISCOVERYREQUEST']._serialized_start=18531 + _globals['_REFRESHOIDCDISCOVERYREQUEST']._serialized_end=18646 + _globals['_REFRESHOIDCDISCOVERYRESPONSE']._serialized_start=18649 + _globals['_REFRESHOIDCDISCOVERYRESPONSE']._serialized_end=18843 + _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._serialized_start=18797 + _globals['_REFRESHOIDCDISCOVERYRESPONSE_RESULTSENTRY']._serialized_end=18843 + _globals['_LISTOIDCPRESETSREQUEST']._serialized_start=18845 + _globals['_LISTOIDCPRESETSREQUEST']._serialized_end=18869 + _globals['_OIDCPRESETPROTO']._serialized_start=18872 + _globals['_OIDCPRESETPROTO']._serialized_end=19056 + _globals['_LISTOIDCPRESETSRESPONSE']._serialized_start=19058 + _globals['_LISTOIDCPRESETSRESPONSE']._serialized_end=19127 + _globals['_EXPORTRULESPROTO']._serialized_start=19130 + _globals['_EXPORTRULESPROTO']._serialized_end=19364 + _globals['_TRIGGERRULESPROTO']._serialized_start=19367 + _globals['_TRIGGERRULESPROTO']._serialized_end=19503 + _globals['_WORKSPACESETTINGSPROTO']._serialized_start=19506 + _globals['_WORKSPACESETTINGSPROTO']._serialized_end=19799 + _globals['_PROJECTSETTINGSPROTO']._serialized_start=19802 + _globals['_PROJECTSETTINGSPROTO']._serialized_end=20093 + _globals['_PROJECTPROTO']._serialized_start=20096 + _globals['_PROJECTPROTO']._serialized_end=20419 + _globals['_PROJECTMEMBERSHIPPROTO']._serialized_start=20421 + _globals['_PROJECTMEMBERSHIPPROTO']._serialized_end=20543 + _globals['_CREATEPROJECTREQUEST']._serialized_start=20546 + _globals['_CREATEPROJECTREQUEST']._serialized_end=20742 + _globals['_GETPROJECTREQUEST']._serialized_start=20744 + _globals['_GETPROJECTREQUEST']._serialized_end=20783 + _globals['_GETPROJECTBYSLUGREQUEST']._serialized_start=20785 + _globals['_GETPROJECTBYSLUGREQUEST']._serialized_end=20846 + _globals['_LISTPROJECTSREQUEST']._serialized_start=20848 + _globals['_LISTPROJECTSREQUEST']._serialized_end=20948 + _globals['_LISTPROJECTSRESPONSE']._serialized_start=20950 + _globals['_LISTPROJECTSRESPONSE']._serialized_end=21035 + _globals['_UPDATEPROJECTREQUEST']._serialized_start=21038 + _globals['_UPDATEPROJECTREQUEST']._serialized_end=21246 + _globals['_ARCHIVEPROJECTREQUEST']._serialized_start=21248 + _globals['_ARCHIVEPROJECTREQUEST']._serialized_end=21291 + _globals['_RESTOREPROJECTREQUEST']._serialized_start=21293 + _globals['_RESTOREPROJECTREQUEST']._serialized_end=21336 + _globals['_DELETEPROJECTREQUEST']._serialized_start=21338 + _globals['_DELETEPROJECTREQUEST']._serialized_end=21380 + _globals['_DELETEPROJECTRESPONSE']._serialized_start=21382 + _globals['_DELETEPROJECTRESPONSE']._serialized_end=21422 + _globals['_SETACTIVEPROJECTREQUEST']._serialized_start=21424 + _globals['_SETACTIVEPROJECTREQUEST']._serialized_end=21491 + _globals['_SETACTIVEPROJECTRESPONSE']._serialized_start=21493 + _globals['_SETACTIVEPROJECTRESPONSE']._serialized_end=21519 + _globals['_GETACTIVEPROJECTREQUEST']._serialized_start=21521 + _globals['_GETACTIVEPROJECTREQUEST']._serialized_end=21568 + _globals['_GETACTIVEPROJECTRESPONSE']._serialized_start=21570 + _globals['_GETACTIVEPROJECTRESPONSE']._serialized_end=21677 + _globals['_ADDPROJECTMEMBERREQUEST']._serialized_start=21679 + _globals['_ADDPROJECTMEMBERREQUEST']._serialized_end=21783 + _globals['_UPDATEPROJECTMEMBERROLEREQUEST']._serialized_start=21785 + _globals['_UPDATEPROJECTMEMBERROLEREQUEST']._serialized_end=21896 + _globals['_REMOVEPROJECTMEMBERREQUEST']._serialized_start=21898 + _globals['_REMOVEPROJECTMEMBERREQUEST']._serialized_end=21963 + _globals['_REMOVEPROJECTMEMBERRESPONSE']._serialized_start=21965 + _globals['_REMOVEPROJECTMEMBERRESPONSE']._serialized_end=22011 + _globals['_LISTPROJECTMEMBERSREQUEST']._serialized_start=22013 + _globals['_LISTPROJECTMEMBERSREQUEST']._serialized_end=22091 + _globals['_LISTPROJECTMEMBERSRESPONSE']._serialized_start=22093 + _globals['_LISTPROJECTMEMBERSRESPONSE']._serialized_end=22193 + _globals['_GETCURRENTUSERREQUEST']._serialized_start=22195 + _globals['_GETCURRENTUSERREQUEST']._serialized_end=22218 + _globals['_GETCURRENTUSERRESPONSE']._serialized_start=22221 + _globals['_GETCURRENTUSERRESPONSE']._serialized_end=22408 + _globals['_WORKSPACEPROTO']._serialized_start=22410 + _globals['_WORKSPACEPROTO']._serialized_end=22500 + _globals['_LISTWORKSPACESREQUEST']._serialized_start=22502 + _globals['_LISTWORKSPACESREQUEST']._serialized_end=22556 + _globals['_LISTWORKSPACESRESPONSE']._serialized_start=22558 + _globals['_LISTWORKSPACESRESPONSE']._serialized_end=22649 + _globals['_SWITCHWORKSPACEREQUEST']._serialized_start=22651 + _globals['_SWITCHWORKSPACEREQUEST']._serialized_end=22697 + _globals['_SWITCHWORKSPACERESPONSE']._serialized_start=22699 + _globals['_SWITCHWORKSPACERESPONSE']._serialized_end=22809 + _globals['_GETWORKSPACESETTINGSREQUEST']._serialized_start=22811 + _globals['_GETWORKSPACESETTINGSREQUEST']._serialized_end=22862 + _globals['_UPDATEWORKSPACESETTINGSREQUEST']._serialized_start=22864 + _globals['_UPDATEWORKSPACESETTINGSREQUEST']._serialized_end=22970 + _globals['_TASKPROTO']._serialized_start=22973 + _globals['_TASKPROTO']._serialized_end=23223 + _globals['_TASKWITHMEETINGPROTO']._serialized_start=23226 + _globals['_TASKWITHMEETINGPROTO']._serialized_end=23354 + _globals['_LISTTASKSREQUEST']._serialized_start=23357 + _globals['_LISTTASKSREQUEST']._serialized_end=23552 + _globals['_LISTTASKSRESPONSE']._serialized_start=23554 + _globals['_LISTTASKSRESPONSE']._serialized_end=23641 + _globals['_UPDATETASKREQUEST']._serialized_start=23644 + _globals['_UPDATETASKREQUEST']._serialized_end=23801 + _globals['_UPDATETASKRESPONSE']._serialized_start=23803 + _globals['_UPDATETASKRESPONSE']._serialized_end=23858 + _globals['_GETANALYTICSOVERVIEWREQUEST']._serialized_start=23861 + _globals['_GETANALYTICSOVERVIEWREQUEST']._serialized_end=23989 + _globals['_DAILYMEETINGSTATSPROTO']._serialized_start=23991 + _globals['_DAILYMEETINGSTATSPROTO']._serialized_end=24091 + _globals['_GETANALYTICSOVERVIEWRESPONSE']._serialized_start=24094 + _globals['_GETANALYTICSOVERVIEWRESPONSE']._serialized_end=24289 + _globals['_SPEAKERSTATPROTO']._serialized_start=24292 + _globals['_SPEAKERSTATPROTO']._serialized_end=24442 + _globals['_LISTSPEAKERSTATSREQUEST']._serialized_start=24444 + _globals['_LISTSPEAKERSTATSREQUEST']._serialized_end=24568 + _globals['_LISTSPEAKERSTATSRESPONSE']._serialized_start=24570 + _globals['_LISTSPEAKERSTATSRESPONSE']._serialized_end=24642 + _globals['_ENTITYCATEGORYSTATPROTO']._serialized_start=24644 + _globals['_ENTITYCATEGORYSTATPROTO']._serialized_end=24726 + _globals['_TOPENTITYPROTO']._serialized_start=24728 + _globals['_TOPENTITYPROTO']._serialized_end=24822 + _globals['_GETENTITYANALYTICSREQUEST']._serialized_start=24825 + _globals['_GETENTITYANALYTICSREQUEST']._serialized_end=24970 + _globals['_GETENTITYANALYTICSRESPONSE']._serialized_start=24973 + _globals['_GETENTITYANALYTICSRESPONSE']._serialized_end=25153 + _globals['_NOTEFLOWSERVICE']._serialized_start=27009 + _globals['_NOTEFLOWSERVICE']._serialized_end=35678 # @@protoc_insertion_point(module_scope) diff --git a/src/noteflow/grpc/proto/noteflow_pb2.pyi b/src/noteflow/grpc/proto/noteflow_pb2.pyi index fbd9f5c..9e6cd42 100644 --- a/src/noteflow/grpc/proto/noteflow_pb2.pyi +++ b/src/noteflow/grpc/proto/noteflow_pb2.pyi @@ -1,8970 +1,2418 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -NoteFlow gRPC Service Definition -Provides real-time ASR streaming and meeting management -""" - -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -class _UpdateType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _UpdateTypeEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_UpdateType.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - UPDATE_TYPE_UNSPECIFIED: _UpdateType.ValueType # 0 - UPDATE_TYPE_PARTIAL: _UpdateType.ValueType # 1 - """Tentative, may change""" - UPDATE_TYPE_FINAL: _UpdateType.ValueType # 2 - """Confirmed segment""" - UPDATE_TYPE_VAD_START: _UpdateType.ValueType # 3 - """Voice activity started""" - UPDATE_TYPE_VAD_END: _UpdateType.ValueType # 4 - """Voice activity ended""" - -class UpdateType(_UpdateType, metaclass=_UpdateTypeEnumTypeWrapper): ... - -UPDATE_TYPE_UNSPECIFIED: UpdateType.ValueType # 0 -UPDATE_TYPE_PARTIAL: UpdateType.ValueType # 1 -"""Tentative, may change""" -UPDATE_TYPE_FINAL: UpdateType.ValueType # 2 -"""Confirmed segment""" -UPDATE_TYPE_VAD_START: UpdateType.ValueType # 3 -"""Voice activity started""" -UPDATE_TYPE_VAD_END: UpdateType.ValueType # 4 -"""Voice activity ended""" -Global___UpdateType: typing_extensions.TypeAlias = UpdateType - -class _MeetingState: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _MeetingStateEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_MeetingState.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - MEETING_STATE_UNSPECIFIED: _MeetingState.ValueType # 0 - MEETING_STATE_CREATED: _MeetingState.ValueType # 1 - """Created but not started""" - MEETING_STATE_RECORDING: _MeetingState.ValueType # 2 - """Actively recording""" - MEETING_STATE_STOPPED: _MeetingState.ValueType # 3 - """Recording stopped, processing may continue""" - MEETING_STATE_COMPLETED: _MeetingState.ValueType # 4 - """All processing complete""" - MEETING_STATE_ERROR: _MeetingState.ValueType # 5 - """Error occurred""" - -class MeetingState(_MeetingState, metaclass=_MeetingStateEnumTypeWrapper): ... - -MEETING_STATE_UNSPECIFIED: MeetingState.ValueType # 0 -MEETING_STATE_CREATED: MeetingState.ValueType # 1 -"""Created but not started""" -MEETING_STATE_RECORDING: MeetingState.ValueType # 2 -"""Actively recording""" -MEETING_STATE_STOPPED: MeetingState.ValueType # 3 -"""Recording stopped, processing may continue""" -MEETING_STATE_COMPLETED: MeetingState.ValueType # 4 -"""All processing complete""" -MEETING_STATE_ERROR: MeetingState.ValueType # 5 -"""Error occurred""" -Global___MeetingState: typing_extensions.TypeAlias = MeetingState - -class _SortOrder: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _SortOrderEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_SortOrder.ValueType], builtins.type -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SORT_ORDER_UNSPECIFIED: _SortOrder.ValueType # 0 - SORT_ORDER_CREATED_DESC: _SortOrder.ValueType # 1 - """Newest first (default)""" - SORT_ORDER_CREATED_ASC: _SortOrder.ValueType # 2 - """Oldest first""" - -class SortOrder(_SortOrder, metaclass=_SortOrderEnumTypeWrapper): ... - -SORT_ORDER_UNSPECIFIED: SortOrder.ValueType # 0 -SORT_ORDER_CREATED_DESC: SortOrder.ValueType # 1 -"""Newest first (default)""" -SORT_ORDER_CREATED_ASC: SortOrder.ValueType # 2 -"""Oldest first""" -Global___SortOrder: typing_extensions.TypeAlias = SortOrder - -class _Priority: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _PriorityEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Priority.ValueType], builtins.type -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - PRIORITY_UNSPECIFIED: _Priority.ValueType # 0 - PRIORITY_LOW: _Priority.ValueType # 1 - PRIORITY_MEDIUM: _Priority.ValueType # 2 - PRIORITY_HIGH: _Priority.ValueType # 3 - -class Priority(_Priority, metaclass=_PriorityEnumTypeWrapper): ... - -PRIORITY_UNSPECIFIED: Priority.ValueType # 0 -PRIORITY_LOW: Priority.ValueType # 1 -PRIORITY_MEDIUM: Priority.ValueType # 2 -PRIORITY_HIGH: Priority.ValueType # 3 -Global___Priority: typing_extensions.TypeAlias = Priority - -class _AsrDevice: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _AsrDeviceEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_AsrDevice.ValueType], builtins.type -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - ASR_DEVICE_UNSPECIFIED: _AsrDevice.ValueType # 0 - ASR_DEVICE_CPU: _AsrDevice.ValueType # 1 - ASR_DEVICE_CUDA: _AsrDevice.ValueType # 2 - ASR_DEVICE_ROCM: _AsrDevice.ValueType # 3 - -class AsrDevice(_AsrDevice, metaclass=_AsrDeviceEnumTypeWrapper): - """============================================================================= - ASR Configuration Messages (Sprint 19) - ============================================================================= - - Valid ASR devices - """ - -ASR_DEVICE_UNSPECIFIED: AsrDevice.ValueType # 0 -ASR_DEVICE_CPU: AsrDevice.ValueType # 1 -ASR_DEVICE_CUDA: AsrDevice.ValueType # 2 -ASR_DEVICE_ROCM: AsrDevice.ValueType # 3 -Global___AsrDevice: typing_extensions.TypeAlias = AsrDevice - -class _AsrComputeType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _AsrComputeTypeEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_AsrComputeType.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - ASR_COMPUTE_TYPE_UNSPECIFIED: _AsrComputeType.ValueType # 0 - ASR_COMPUTE_TYPE_INT8: _AsrComputeType.ValueType # 1 - ASR_COMPUTE_TYPE_FLOAT16: _AsrComputeType.ValueType # 2 - ASR_COMPUTE_TYPE_FLOAT32: _AsrComputeType.ValueType # 3 - -class AsrComputeType(_AsrComputeType, metaclass=_AsrComputeTypeEnumTypeWrapper): - """Valid ASR compute types""" - -ASR_COMPUTE_TYPE_UNSPECIFIED: AsrComputeType.ValueType # 0 -ASR_COMPUTE_TYPE_INT8: AsrComputeType.ValueType # 1 -ASR_COMPUTE_TYPE_FLOAT16: AsrComputeType.ValueType # 2 -ASR_COMPUTE_TYPE_FLOAT32: AsrComputeType.ValueType # 3 -Global___AsrComputeType: typing_extensions.TypeAlias = AsrComputeType - -class _AnnotationType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _AnnotationTypeEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_AnnotationType.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - ANNOTATION_TYPE_UNSPECIFIED: _AnnotationType.ValueType # 0 - ANNOTATION_TYPE_ACTION_ITEM: _AnnotationType.ValueType # 1 - ANNOTATION_TYPE_DECISION: _AnnotationType.ValueType # 2 - ANNOTATION_TYPE_NOTE: _AnnotationType.ValueType # 3 - ANNOTATION_TYPE_RISK: _AnnotationType.ValueType # 4 - -class AnnotationType(_AnnotationType, metaclass=_AnnotationTypeEnumTypeWrapper): - """============================================================================= - Annotation Messages - ============================================================================= - """ - -ANNOTATION_TYPE_UNSPECIFIED: AnnotationType.ValueType # 0 -ANNOTATION_TYPE_ACTION_ITEM: AnnotationType.ValueType # 1 -ANNOTATION_TYPE_DECISION: AnnotationType.ValueType # 2 -ANNOTATION_TYPE_NOTE: AnnotationType.ValueType # 3 -ANNOTATION_TYPE_RISK: AnnotationType.ValueType # 4 -Global___AnnotationType: typing_extensions.TypeAlias = AnnotationType - -class _ExportFormat: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _ExportFormatEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ExportFormat.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - EXPORT_FORMAT_UNSPECIFIED: _ExportFormat.ValueType # 0 - EXPORT_FORMAT_MARKDOWN: _ExportFormat.ValueType # 1 - EXPORT_FORMAT_HTML: _ExportFormat.ValueType # 2 - EXPORT_FORMAT_PDF: _ExportFormat.ValueType # 3 - """PDF export (Sprint 3)""" - -class ExportFormat(_ExportFormat, metaclass=_ExportFormatEnumTypeWrapper): - """============================================================================= - Export Messages - ============================================================================= - """ - -EXPORT_FORMAT_UNSPECIFIED: ExportFormat.ValueType # 0 -EXPORT_FORMAT_MARKDOWN: ExportFormat.ValueType # 1 -EXPORT_FORMAT_HTML: ExportFormat.ValueType # 2 -EXPORT_FORMAT_PDF: ExportFormat.ValueType # 3 -"""PDF export (Sprint 3)""" -Global___ExportFormat: typing_extensions.TypeAlias = ExportFormat - -class _JobStatus: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _JobStatusEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_JobStatus.ValueType], builtins.type -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - JOB_STATUS_UNSPECIFIED: _JobStatus.ValueType # 0 - JOB_STATUS_QUEUED: _JobStatus.ValueType # 1 - JOB_STATUS_RUNNING: _JobStatus.ValueType # 2 - JOB_STATUS_COMPLETED: _JobStatus.ValueType # 3 - JOB_STATUS_FAILED: _JobStatus.ValueType # 4 - JOB_STATUS_CANCELLED: _JobStatus.ValueType # 5 - -class JobStatus(_JobStatus, metaclass=_JobStatusEnumTypeWrapper): ... - -JOB_STATUS_UNSPECIFIED: JobStatus.ValueType # 0 -JOB_STATUS_QUEUED: JobStatus.ValueType # 1 -JOB_STATUS_RUNNING: JobStatus.ValueType # 2 -JOB_STATUS_COMPLETED: JobStatus.ValueType # 3 -JOB_STATUS_FAILED: JobStatus.ValueType # 4 -JOB_STATUS_CANCELLED: JobStatus.ValueType # 5 -Global___JobStatus: typing_extensions.TypeAlias = JobStatus - -class _SyncErrorCode: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _SyncErrorCodeEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_SyncErrorCode.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SYNC_ERROR_CODE_UNSPECIFIED: _SyncErrorCode.ValueType # 0 - """Default/unspecified""" - SYNC_ERROR_CODE_AUTH_REQUIRED: _SyncErrorCode.ValueType # 1 - """Authentication required - token expired or revoked, needs re-auth""" - SYNC_ERROR_CODE_PROVIDER_ERROR: _SyncErrorCode.ValueType # 2 - """External provider error - API failure, transient error""" - SYNC_ERROR_CODE_INTERNAL_ERROR: _SyncErrorCode.ValueType # 3 - """Internal server error - unexpected failure in sync logic""" - SYNC_ERROR_CODE_UNKNOWN: _SyncErrorCode.ValueType # 4 - """Unknown error - fallback for unclassified errors""" - -class SyncErrorCode(_SyncErrorCode, metaclass=_SyncErrorCodeEnumTypeWrapper): - """Structured error codes for sync failures (enables programmatic error handling)""" - -SYNC_ERROR_CODE_UNSPECIFIED: SyncErrorCode.ValueType # 0 -"""Default/unspecified""" -SYNC_ERROR_CODE_AUTH_REQUIRED: SyncErrorCode.ValueType # 1 -"""Authentication required - token expired or revoked, needs re-auth""" -SYNC_ERROR_CODE_PROVIDER_ERROR: SyncErrorCode.ValueType # 2 -"""External provider error - API failure, transient error""" -SYNC_ERROR_CODE_INTERNAL_ERROR: SyncErrorCode.ValueType # 3 -"""Internal server error - unexpected failure in sync logic""" -SYNC_ERROR_CODE_UNKNOWN: SyncErrorCode.ValueType # 4 -"""Unknown error - fallback for unclassified errors""" -Global___SyncErrorCode: typing_extensions.TypeAlias = SyncErrorCode - -class _ProcessingStepStatus: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _ProcessingStepStatusEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ProcessingStepStatus.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - PROCESSING_STEP_UNSPECIFIED: _ProcessingStepStatus.ValueType # 0 - PROCESSING_STEP_PENDING: _ProcessingStepStatus.ValueType # 1 - """Not yet started""" - PROCESSING_STEP_RUNNING: _ProcessingStepStatus.ValueType # 2 - """Currently processing""" - PROCESSING_STEP_COMPLETED: _ProcessingStepStatus.ValueType # 3 - """Completed successfully""" - PROCESSING_STEP_FAILED: _ProcessingStepStatus.ValueType # 4 - """Failed with error""" - PROCESSING_STEP_SKIPPED: _ProcessingStepStatus.ValueType # 5 - """Skipped (e.g., feature disabled)""" - -class ProcessingStepStatus(_ProcessingStepStatus, metaclass=_ProcessingStepStatusEnumTypeWrapper): - """============================================================================= - Post-Processing Status Messages (GAP-W05) - ============================================================================= - - Status of an individual processing step (summary, entities, diarization) - """ - -PROCESSING_STEP_UNSPECIFIED: ProcessingStepStatus.ValueType # 0 -PROCESSING_STEP_PENDING: ProcessingStepStatus.ValueType # 1 -"""Not yet started""" -PROCESSING_STEP_RUNNING: ProcessingStepStatus.ValueType # 2 -"""Currently processing""" -PROCESSING_STEP_COMPLETED: ProcessingStepStatus.ValueType # 3 -"""Completed successfully""" -PROCESSING_STEP_FAILED: ProcessingStepStatus.ValueType # 4 -"""Failed with error""" -PROCESSING_STEP_SKIPPED: ProcessingStepStatus.ValueType # 5 -"""Skipped (e.g., feature disabled)""" -Global___ProcessingStepStatus: typing_extensions.TypeAlias = ProcessingStepStatus - -class _ProjectRoleProto: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _ProjectRoleProtoEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ProjectRoleProto.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - PROJECT_ROLE_UNSPECIFIED: _ProjectRoleProto.ValueType # 0 - PROJECT_ROLE_VIEWER: _ProjectRoleProto.ValueType # 1 - """Read meetings, artifacts, run Q&A""" - PROJECT_ROLE_EDITOR: _ProjectRoleProto.ValueType # 2 - """+ Create/edit meetings, upload artifacts""" - PROJECT_ROLE_ADMIN: _ProjectRoleProto.ValueType # 3 - """+ Manage members, settings, rules""" - -class ProjectRoleProto(_ProjectRoleProto, metaclass=_ProjectRoleProtoEnumTypeWrapper): - """============================================================================= - Project Management Messages (Sprint 18) - ============================================================================= - - Project role within a project (access control) - """ - -PROJECT_ROLE_UNSPECIFIED: ProjectRoleProto.ValueType # 0 -PROJECT_ROLE_VIEWER: ProjectRoleProto.ValueType # 1 -"""Read meetings, artifacts, run Q&A""" -PROJECT_ROLE_EDITOR: ProjectRoleProto.ValueType # 2 -"""+ Create/edit meetings, upload artifacts""" -PROJECT_ROLE_ADMIN: ProjectRoleProto.ValueType # 3 -"""+ Manage members, settings, rules""" -Global___ProjectRoleProto: typing_extensions.TypeAlias = ProjectRoleProto - -@typing.final -class AudioChunk(google.protobuf.message.Message): - """============================================================================= - Audio Streaming Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - AUDIO_DATA_FIELD_NUMBER: builtins.int - TIMESTAMP_FIELD_NUMBER: builtins.int - SAMPLE_RATE_FIELD_NUMBER: builtins.int - CHANNELS_FIELD_NUMBER: builtins.int - CHUNK_SEQUENCE_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID this audio belongs to""" - audio_data: builtins.bytes - """Raw audio data (float32, mono, 16kHz expected)""" - timestamp: builtins.float - """Timestamp when audio was captured (monotonic, seconds)""" - sample_rate: builtins.int - """Sample rate in Hz (default 16000)""" - channels: builtins.int - """Number of channels (default 1 for mono)""" - chunk_sequence: builtins.int - """Sequence number for acknowledgment tracking (monotonically increasing per stream)""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - audio_data: builtins.bytes = ..., - timestamp: builtins.float = ..., - sample_rate: builtins.int = ..., - channels: builtins.int = ..., - chunk_sequence: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "audio_data", - b"audio_data", - "channels", - b"channels", - "chunk_sequence", - b"chunk_sequence", - "meeting_id", - b"meeting_id", - "sample_rate", - b"sample_rate", - "timestamp", - b"timestamp", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___AudioChunk: typing_extensions.TypeAlias = AudioChunk - -@typing.final -class CongestionInfo(google.protobuf.message.Message): - """Congestion information for backpressure signaling (Phase 3)""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROCESSING_DELAY_MS_FIELD_NUMBER: builtins.int - QUEUE_DEPTH_FIELD_NUMBER: builtins.int - THROTTLE_RECOMMENDED_FIELD_NUMBER: builtins.int - processing_delay_ms: builtins.int - """Time from chunk receipt to transcription processing (milliseconds)""" - queue_depth: builtins.int - """Number of chunks waiting to be processed""" - throttle_recommended: builtins.bool - """Signal that client should reduce sending rate""" - def __init__( - self, - *, - processing_delay_ms: builtins.int = ..., - queue_depth: builtins.int = ..., - throttle_recommended: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "processing_delay_ms", - b"processing_delay_ms", - "queue_depth", - b"queue_depth", - "throttle_recommended", - b"throttle_recommended", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CongestionInfo: typing_extensions.TypeAlias = CongestionInfo - -@typing.final -class TranscriptUpdate(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - UPDATE_TYPE_FIELD_NUMBER: builtins.int - PARTIAL_TEXT_FIELD_NUMBER: builtins.int - SEGMENT_FIELD_NUMBER: builtins.int - SERVER_TIMESTAMP_FIELD_NUMBER: builtins.int - ACK_SEQUENCE_FIELD_NUMBER: builtins.int - CONGESTION_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID this transcript belongs to""" - update_type: Global___UpdateType.ValueType - """Type of update""" - partial_text: builtins.str - """For partial updates - tentative transcript text""" - server_timestamp: builtins.float - """Server-side processing timestamp""" - ack_sequence: builtins.int - """Acknowledgment: highest contiguous chunk sequence received (optional)""" - @property - def segment(self) -> Global___FinalSegment: - """For final segments - confirmed transcript""" - - @property - def congestion(self) -> Global___CongestionInfo: - """Congestion info for backpressure signaling (optional)""" - - def __init__( - self, - *, - meeting_id: builtins.str = ..., - update_type: Global___UpdateType.ValueType = ..., - partial_text: builtins.str = ..., - segment: Global___FinalSegment | None = ..., - server_timestamp: builtins.float = ..., - ack_sequence: builtins.int | None = ..., - congestion: Global___CongestionInfo | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_ack_sequence", - b"_ack_sequence", - "_congestion", - b"_congestion", - "ack_sequence", - b"ack_sequence", - "congestion", - b"congestion", - "segment", - b"segment", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_ack_sequence", - b"_ack_sequence", - "_congestion", - b"_congestion", - "ack_sequence", - b"ack_sequence", - "congestion", - b"congestion", - "meeting_id", - b"meeting_id", - "partial_text", - b"partial_text", - "segment", - b"segment", - "server_timestamp", - b"server_timestamp", - "update_type", - b"update_type", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__ack_sequence: typing_extensions.TypeAlias = typing.Literal[ - "ack_sequence" - ] - _WhichOneofArgType__ack_sequence: typing_extensions.TypeAlias = typing.Literal[ - "_ack_sequence", b"_ack_sequence" - ] - _WhichOneofReturnType__congestion: typing_extensions.TypeAlias = typing.Literal["congestion"] - _WhichOneofArgType__congestion: typing_extensions.TypeAlias = typing.Literal[ - "_congestion", b"_congestion" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__ack_sequence - ) -> _WhichOneofReturnType__ack_sequence | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__congestion - ) -> _WhichOneofReturnType__congestion | None: ... - -Global___TranscriptUpdate: typing_extensions.TypeAlias = TranscriptUpdate - -@typing.final -class FinalSegment(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SEGMENT_ID_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - WORDS_FIELD_NUMBER: builtins.int - LANGUAGE_FIELD_NUMBER: builtins.int - LANGUAGE_CONFIDENCE_FIELD_NUMBER: builtins.int - AVG_LOGPROB_FIELD_NUMBER: builtins.int - NO_SPEECH_PROB_FIELD_NUMBER: builtins.int - SPEAKER_ID_FIELD_NUMBER: builtins.int - SPEAKER_CONFIDENCE_FIELD_NUMBER: builtins.int - segment_id: builtins.int - """Segment ID (sequential within meeting)""" - text: builtins.str - """Transcript text""" - start_time: builtins.float - """Start time relative to meeting start (seconds)""" - end_time: builtins.float - """End time relative to meeting start (seconds)""" - language: builtins.str - """Detected language""" - language_confidence: builtins.float - """Language detection confidence (0.0-1.0)""" - avg_logprob: builtins.float - """Average log probability (quality indicator)""" - no_speech_prob: builtins.float - """Probability that segment contains no speech""" - speaker_id: builtins.str - """Speaker identification (from diarization)""" - speaker_confidence: builtins.float - """Speaker assignment confidence (0.0-1.0)""" - @property - def words( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___WordTiming]: - """Word-level timestamps""" - - def __init__( - self, - *, - segment_id: builtins.int = ..., - text: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - words: collections.abc.Iterable[Global___WordTiming] | None = ..., - language: builtins.str = ..., - language_confidence: builtins.float = ..., - avg_logprob: builtins.float = ..., - no_speech_prob: builtins.float = ..., - speaker_id: builtins.str = ..., - speaker_confidence: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "avg_logprob", - b"avg_logprob", - "end_time", - b"end_time", - "language", - b"language", - "language_confidence", - b"language_confidence", - "no_speech_prob", - b"no_speech_prob", - "segment_id", - b"segment_id", - "speaker_confidence", - b"speaker_confidence", - "speaker_id", - b"speaker_id", - "start_time", - b"start_time", - "text", - b"text", - "words", - b"words", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___FinalSegment: typing_extensions.TypeAlias = FinalSegment - -@typing.final -class WordTiming(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORD_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - PROBABILITY_FIELD_NUMBER: builtins.int - word: builtins.str - start_time: builtins.float - end_time: builtins.float - probability: builtins.float - def __init__( - self, - *, - word: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - probability: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "end_time", - b"end_time", - "probability", - b"probability", - "start_time", - b"start_time", - "word", - b"word", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___WordTiming: typing_extensions.TypeAlias = WordTiming - -@typing.final -class Meeting(google.protobuf.message.Message): - """============================================================================= - Meeting Management Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - ID_FIELD_NUMBER: builtins.int - TITLE_FIELD_NUMBER: builtins.int - STATE_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - STARTED_AT_FIELD_NUMBER: builtins.int - ENDED_AT_FIELD_NUMBER: builtins.int - DURATION_SECONDS_FIELD_NUMBER: builtins.int - SEGMENTS_FIELD_NUMBER: builtins.int - SUMMARY_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - PROCESSING_STATUS_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique meeting identifier""" - title: builtins.str - """User-provided title""" - state: Global___MeetingState.ValueType - """Meeting state""" - created_at: builtins.float - """Creation timestamp (Unix epoch seconds)""" - started_at: builtins.float - """Start timestamp (when recording began)""" - ended_at: builtins.float - """End timestamp (when recording stopped)""" - duration_seconds: builtins.float - """Duration in seconds""" - project_id: builtins.str - """Optional project scope""" - @property - def segments( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___FinalSegment]: - """Full transcript segments""" - - @property - def summary(self) -> Global___Summary: - """Generated summary (if available)""" - - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Metadata""" - - @property - def processing_status(self) -> Global___ProcessingStatus: - """Post-processing status (GAP-W05)""" - - def __init__( - self, - *, - id: builtins.str = ..., - title: builtins.str = ..., - state: Global___MeetingState.ValueType = ..., - created_at: builtins.float = ..., - started_at: builtins.float = ..., - ended_at: builtins.float = ..., - duration_seconds: builtins.float = ..., - segments: collections.abc.Iterable[Global___FinalSegment] | None = ..., - summary: Global___Summary | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - project_id: builtins.str | None = ..., - processing_status: Global___ProcessingStatus | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "processing_status", - b"processing_status", - "project_id", - b"project_id", - "summary", - b"summary", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "created_at", - b"created_at", - "duration_seconds", - b"duration_seconds", - "ended_at", - b"ended_at", - "id", - b"id", - "metadata", - b"metadata", - "processing_status", - b"processing_status", - "project_id", - b"project_id", - "segments", - b"segments", - "started_at", - b"started_at", - "state", - b"state", - "summary", - b"summary", - "title", - b"title", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___Meeting: typing_extensions.TypeAlias = Meeting - -@typing.final -class CreateMeetingRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - TITLE_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - title: builtins.str - """Optional title (generated if not provided)""" - project_id: builtins.str - """Optional project scope (defaults to active project)""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Optional metadata""" - - def __init__( - self, - *, - title: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - project_id: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project_id", b"project_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "metadata", - b"metadata", - "project_id", - b"project_id", - "title", - b"title", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___CreateMeetingRequest: typing_extensions.TypeAlias = CreateMeetingRequest - -@typing.final -class StopMeetingRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - def __init__( - self, - *, - meeting_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meeting_id", b"meeting_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___StopMeetingRequest: typing_extensions.TypeAlias = StopMeetingRequest - -@typing.final -class ListMeetingsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATES_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - SORT_ORDER_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - PROJECT_IDS_FIELD_NUMBER: builtins.int - limit: builtins.int - """Pagination""" - offset: builtins.int - sort_order: Global___SortOrder.ValueType - """Sort order""" - project_id: builtins.str - """Optional project filter (defaults to active project if omitted)""" - @property - def states( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[ - Global___MeetingState.ValueType - ]: - """Optional filter by state""" - - @property - def project_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional project filter for multiple projects (overrides project_id when provided)""" - - def __init__( - self, - *, - states: collections.abc.Iterable[Global___MeetingState.ValueType] | None = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - sort_order: Global___SortOrder.ValueType = ..., - project_id: builtins.str | None = ..., - project_ids: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project_id", b"project_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "limit", - b"limit", - "offset", - b"offset", - "project_id", - b"project_id", - "project_ids", - b"project_ids", - "sort_order", - b"sort_order", - "states", - b"states", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___ListMeetingsRequest: typing_extensions.TypeAlias = ListMeetingsRequest - -@typing.final -class ListMeetingsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETINGS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - @property - def meetings( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___Meeting]: ... - def __init__( - self, - *, - meetings: collections.abc.Iterable[Global___Meeting] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "meetings", b"meetings", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListMeetingsResponse: typing_extensions.TypeAlias = ListMeetingsResponse - -@typing.final -class GetMeetingRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - INCLUDE_SEGMENTS_FIELD_NUMBER: builtins.int - INCLUDE_SUMMARY_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - include_segments: builtins.bool - """Whether to include full transcript segments""" - include_summary: builtins.bool - """Whether to include summary""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - include_segments: builtins.bool = ..., - include_summary: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "include_segments", - b"include_segments", - "include_summary", - b"include_summary", - "meeting_id", - b"meeting_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetMeetingRequest: typing_extensions.TypeAlias = GetMeetingRequest - -@typing.final -class DeleteMeetingRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - def __init__( - self, - *, - meeting_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meeting_id", b"meeting_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteMeetingRequest: typing_extensions.TypeAlias = DeleteMeetingRequest - -@typing.final -class DeleteMeetingResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteMeetingResponse: typing_extensions.TypeAlias = DeleteMeetingResponse - -@typing.final -class Summary(google.protobuf.message.Message): - """============================================================================= - Summary Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - EXECUTIVE_SUMMARY_FIELD_NUMBER: builtins.int - KEY_POINTS_FIELD_NUMBER: builtins.int - ACTION_ITEMS_FIELD_NUMBER: builtins.int - GENERATED_AT_FIELD_NUMBER: builtins.int - MODEL_VERSION_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting this summary belongs to""" - executive_summary: builtins.str - """Executive summary (2-3 sentences)""" - generated_at: builtins.float - """Generated timestamp""" - model_version: builtins.str - """Model/version used for generation""" - @property - def key_points( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___KeyPoint]: - """Key points / highlights""" - - @property - def action_items( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___ActionItem]: - """Action items extracted""" - - def __init__( - self, - *, - meeting_id: builtins.str = ..., - executive_summary: builtins.str = ..., - key_points: collections.abc.Iterable[Global___KeyPoint] | None = ..., - action_items: collections.abc.Iterable[Global___ActionItem] | None = ..., - generated_at: builtins.float = ..., - model_version: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "action_items", - b"action_items", - "executive_summary", - b"executive_summary", - "generated_at", - b"generated_at", - "key_points", - b"key_points", - "meeting_id", - b"meeting_id", - "model_version", - b"model_version", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___Summary: typing_extensions.TypeAlias = Summary - -@typing.final -class KeyPoint(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEXT_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - text: builtins.str - """The key point text""" - start_time: builtins.float - """Timestamp range this point covers""" - end_time: builtins.float - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Segment IDs that support this point (evidence linking)""" - - def __init__( - self, - *, - text: builtins.str = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "end_time", - b"end_time", - "segment_ids", - b"segment_ids", - "start_time", - b"start_time", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___KeyPoint: typing_extensions.TypeAlias = KeyPoint - -@typing.final -class ActionItem(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEXT_FIELD_NUMBER: builtins.int - ASSIGNEE_FIELD_NUMBER: builtins.int - DUE_DATE_FIELD_NUMBER: builtins.int - PRIORITY_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - text: builtins.str - """Action item text""" - assignee: builtins.str - """Assigned to (if mentioned)""" - due_date: builtins.float - """Due date (if mentioned, Unix epoch)""" - priority: Global___Priority.ValueType - """Priority level""" - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Segment IDs that mention this action""" - - def __init__( - self, - *, - text: builtins.str = ..., - assignee: builtins.str = ..., - due_date: builtins.float = ..., - priority: Global___Priority.ValueType = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "assignee", - b"assignee", - "due_date", - b"due_date", - "priority", - b"priority", - "segment_ids", - b"segment_ids", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ActionItem: typing_extensions.TypeAlias = ActionItem - -@typing.final -class SummarizationOptions(google.protobuf.message.Message): - """Summarization style options (Sprint 1)""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TONE_FIELD_NUMBER: builtins.int - FORMAT_FIELD_NUMBER: builtins.int - VERBOSITY_FIELD_NUMBER: builtins.int - TEMPLATE_ID_FIELD_NUMBER: builtins.int - tone: builtins.str - """Tone: professional, casual, technical, friendly""" - format: builtins.str - """Format: bullet_points, narrative, structured, concise""" - verbosity: builtins.str - """Verbosity: minimal, balanced, detailed, comprehensive""" - template_id: builtins.str - """Summarization template ID override (optional)""" - def __init__( - self, - *, - tone: builtins.str = ..., - format: builtins.str = ..., - verbosity: builtins.str = ..., - template_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "format", - b"format", - "template_id", - b"template_id", - "tone", - b"tone", - "verbosity", - b"verbosity", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SummarizationOptions: typing_extensions.TypeAlias = SummarizationOptions - -@typing.final -class GenerateSummaryRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - FORCE_REGENERATE_FIELD_NUMBER: builtins.int - OPTIONS_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - force_regenerate: builtins.bool - """Force regeneration even if summary exists""" - @property - def options(self) -> Global___SummarizationOptions: - """Advanced summarization options (Sprint 1)""" - - def __init__( - self, - *, - meeting_id: builtins.str = ..., - force_regenerate: builtins.bool = ..., - options: Global___SummarizationOptions | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["options", b"options"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "force_regenerate", b"force_regenerate", "meeting_id", b"meeting_id", "options", b"options" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GenerateSummaryRequest: typing_extensions.TypeAlias = GenerateSummaryRequest - -@typing.final -class SummarizationTemplateProto(google.protobuf.message.Message): - """============================================================================= - Summarization Template Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - IS_SYSTEM_FIELD_NUMBER: builtins.int - IS_ARCHIVED_FIELD_NUMBER: builtins.int - CURRENT_VERSION_ID_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - CREATED_BY_FIELD_NUMBER: builtins.int - UPDATED_BY_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique template identifier""" - workspace_id: builtins.str - """Workspace identifier (unset for system templates)""" - name: builtins.str - """Template name""" - description: builtins.str - """Optional description""" - is_system: builtins.bool - """Whether this is a system template""" - is_archived: builtins.bool - """Whether this template is archived""" - current_version_id: builtins.str - """Current version identifier""" - created_at: builtins.int - """Creation timestamp (Unix epoch seconds)""" - updated_at: builtins.int - """Update timestamp (Unix epoch seconds)""" - created_by: builtins.str - """User who created the template""" - updated_by: builtins.str - """User who last updated the template""" - def __init__( - self, - *, - id: builtins.str = ..., - workspace_id: builtins.str | None = ..., - name: builtins.str = ..., - description: builtins.str | None = ..., - is_system: builtins.bool = ..., - is_archived: builtins.bool = ..., - current_version_id: builtins.str | None = ..., - created_at: builtins.int = ..., - updated_at: builtins.int = ..., - created_by: builtins.str | None = ..., - updated_by: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_created_by", - b"_created_by", - "_current_version_id", - b"_current_version_id", - "_description", - b"_description", - "_updated_by", - b"_updated_by", - "_workspace_id", - b"_workspace_id", - "created_by", - b"created_by", - "current_version_id", - b"current_version_id", - "description", - b"description", - "updated_by", - b"updated_by", - "workspace_id", - b"workspace_id", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_created_by", - b"_created_by", - "_current_version_id", - b"_current_version_id", - "_description", - b"_description", - "_updated_by", - b"_updated_by", - "_workspace_id", - b"_workspace_id", - "created_at", - b"created_at", - "created_by", - b"created_by", - "current_version_id", - b"current_version_id", - "description", - b"description", - "id", - b"id", - "is_archived", - b"is_archived", - "is_system", - b"is_system", - "name", - b"name", - "updated_at", - b"updated_at", - "updated_by", - b"updated_by", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__created_by: typing_extensions.TypeAlias = typing.Literal["created_by"] - _WhichOneofArgType__created_by: typing_extensions.TypeAlias = typing.Literal[ - "_created_by", b"_created_by" - ] - _WhichOneofReturnType__current_version_id: typing_extensions.TypeAlias = typing.Literal[ - "current_version_id" - ] - _WhichOneofArgType__current_version_id: typing_extensions.TypeAlias = typing.Literal[ - "_current_version_id", b"_current_version_id" - ] - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - _WhichOneofReturnType__updated_by: typing_extensions.TypeAlias = typing.Literal["updated_by"] - _WhichOneofArgType__updated_by: typing_extensions.TypeAlias = typing.Literal[ - "_updated_by", b"_updated_by" - ] - _WhichOneofReturnType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id" - ] - _WhichOneofArgType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "_workspace_id", b"_workspace_id" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__created_by - ) -> _WhichOneofReturnType__created_by | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__current_version_id - ) -> _WhichOneofReturnType__current_version_id | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__updated_by - ) -> _WhichOneofReturnType__updated_by | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__workspace_id - ) -> _WhichOneofReturnType__workspace_id | None: ... - -Global___SummarizationTemplateProto: typing_extensions.TypeAlias = SummarizationTemplateProto - -@typing.final -class SummarizationTemplateVersionProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - TEMPLATE_ID_FIELD_NUMBER: builtins.int - VERSION_NUMBER_FIELD_NUMBER: builtins.int - CONTENT_FIELD_NUMBER: builtins.int - CHANGE_NOTE_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - CREATED_BY_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique version identifier""" - template_id: builtins.str - """Parent template identifier""" - version_number: builtins.int - """Version number""" - content: builtins.str - """Template content""" - change_note: builtins.str - """Optional change note""" - created_at: builtins.int - """Creation timestamp (Unix epoch seconds)""" - created_by: builtins.str - """User who created the version""" - def __init__( - self, - *, - id: builtins.str = ..., - template_id: builtins.str = ..., - version_number: builtins.int = ..., - content: builtins.str = ..., - change_note: builtins.str | None = ..., - created_at: builtins.int = ..., - created_by: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_created_by", - b"_created_by", - "change_note", - b"change_note", - "created_by", - b"created_by", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_created_by", - b"_created_by", - "change_note", - b"change_note", - "content", - b"content", - "created_at", - b"created_at", - "created_by", - b"created_by", - "id", - b"id", - "template_id", - b"template_id", - "version_number", - b"version_number", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__change_note: typing_extensions.TypeAlias = typing.Literal["change_note"] - _WhichOneofArgType__change_note: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", b"_change_note" - ] - _WhichOneofReturnType__created_by: typing_extensions.TypeAlias = typing.Literal["created_by"] - _WhichOneofArgType__created_by: typing_extensions.TypeAlias = typing.Literal[ - "_created_by", b"_created_by" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__change_note - ) -> _WhichOneofReturnType__change_note | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__created_by - ) -> _WhichOneofReturnType__created_by | None: ... - -Global___SummarizationTemplateVersionProto: typing_extensions.TypeAlias = ( - SummarizationTemplateVersionProto -) - -@typing.final -class ListSummarizationTemplatesRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - INCLUDE_SYSTEM_FIELD_NUMBER: builtins.int - INCLUDE_ARCHIVED_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace identifier to list templates for""" - include_system: builtins.bool - """Include system templates""" - include_archived: builtins.bool - """Include archived templates""" - limit: builtins.int - """Max results to return""" - offset: builtins.int - """Offset for pagination""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - include_system: builtins.bool = ..., - include_archived: builtins.bool = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "include_archived", - b"include_archived", - "include_system", - b"include_system", - "limit", - b"limit", - "offset", - b"offset", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSummarizationTemplatesRequest: typing_extensions.TypeAlias = ( - ListSummarizationTemplatesRequest -) - -@typing.final -class ListSummarizationTemplatesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATES_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - @property - def templates( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___SummarizationTemplateProto - ]: ... - def __init__( - self, - *, - templates: collections.abc.Iterable[Global___SummarizationTemplateProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "templates", b"templates", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSummarizationTemplatesResponse: typing_extensions.TypeAlias = ( - ListSummarizationTemplatesResponse -) - -@typing.final -class GetSummarizationTemplateRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_ID_FIELD_NUMBER: builtins.int - INCLUDE_CURRENT_VERSION_FIELD_NUMBER: builtins.int - template_id: builtins.str - include_current_version: builtins.bool - def __init__( - self, - *, - template_id: builtins.str = ..., - include_current_version: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "include_current_version", b"include_current_version", "template_id", b"template_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetSummarizationTemplateRequest: typing_extensions.TypeAlias = ( - GetSummarizationTemplateRequest -) - -@typing.final -class GetSummarizationTemplateResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_FIELD_NUMBER: builtins.int - CURRENT_VERSION_FIELD_NUMBER: builtins.int - @property - def template(self) -> Global___SummarizationTemplateProto: ... - @property - def current_version(self) -> Global___SummarizationTemplateVersionProto: ... - def __init__( - self, - *, - template: Global___SummarizationTemplateProto | None = ..., - current_version: Global___SummarizationTemplateVersionProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_current_version", - b"_current_version", - "current_version", - b"current_version", - "template", - b"template", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_current_version", - b"_current_version", - "current_version", - b"current_version", - "template", - b"template", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__current_version: typing_extensions.TypeAlias = typing.Literal[ - "current_version" - ] - _WhichOneofArgType__current_version: typing_extensions.TypeAlias = typing.Literal[ - "_current_version", b"_current_version" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__current_version - ) -> _WhichOneofReturnType__current_version | None: ... - -Global___GetSummarizationTemplateResponse: typing_extensions.TypeAlias = ( - GetSummarizationTemplateResponse -) - -@typing.final -class SummarizationTemplateMutationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_FIELD_NUMBER: builtins.int - VERSION_FIELD_NUMBER: builtins.int - @property - def template(self) -> Global___SummarizationTemplateProto: ... - @property - def version(self) -> Global___SummarizationTemplateVersionProto: ... - def __init__( - self, - *, - template: Global___SummarizationTemplateProto | None = ..., - version: Global___SummarizationTemplateVersionProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_version", b"_version", "template", b"template", "version", b"version" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_version", b"_version", "template", b"template", "version", b"version" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__version: typing_extensions.TypeAlias = typing.Literal["version"] - _WhichOneofArgType__version: typing_extensions.TypeAlias = typing.Literal[ - "_version", b"_version" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__version - ) -> _WhichOneofReturnType__version | None: ... - -Global___SummarizationTemplateMutationResponse: typing_extensions.TypeAlias = ( - SummarizationTemplateMutationResponse -) - -@typing.final -class CreateSummarizationTemplateRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - CONTENT_FIELD_NUMBER: builtins.int - CHANGE_NOTE_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - name: builtins.str - description: builtins.str - content: builtins.str - change_note: builtins.str - def __init__( - self, - *, - workspace_id: builtins.str = ..., - name: builtins.str = ..., - description: builtins.str | None = ..., - content: builtins.str = ..., - change_note: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_description", - b"_description", - "change_note", - b"change_note", - "description", - b"description", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_description", - b"_description", - "change_note", - b"change_note", - "content", - b"content", - "description", - b"description", - "name", - b"name", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__change_note: typing_extensions.TypeAlias = typing.Literal["change_note"] - _WhichOneofArgType__change_note: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", b"_change_note" - ] - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__change_note - ) -> _WhichOneofReturnType__change_note | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - -Global___CreateSummarizationTemplateRequest: typing_extensions.TypeAlias = ( - CreateSummarizationTemplateRequest -) - -@typing.final -class UpdateSummarizationTemplateRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - CONTENT_FIELD_NUMBER: builtins.int - CHANGE_NOTE_FIELD_NUMBER: builtins.int - template_id: builtins.str - name: builtins.str - description: builtins.str - content: builtins.str - change_note: builtins.str - def __init__( - self, - *, - template_id: builtins.str = ..., - name: builtins.str | None = ..., - description: builtins.str | None = ..., - content: builtins.str | None = ..., - change_note: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_content", - b"_content", - "_description", - b"_description", - "_name", - b"_name", - "change_note", - b"change_note", - "content", - b"content", - "description", - b"description", - "name", - b"name", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", - b"_change_note", - "_content", - b"_content", - "_description", - b"_description", - "_name", - b"_name", - "change_note", - b"change_note", - "content", - b"content", - "description", - b"description", - "name", - b"name", - "template_id", - b"template_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__change_note: typing_extensions.TypeAlias = typing.Literal["change_note"] - _WhichOneofArgType__change_note: typing_extensions.TypeAlias = typing.Literal[ - "_change_note", b"_change_note" - ] - _WhichOneofReturnType__content: typing_extensions.TypeAlias = typing.Literal["content"] - _WhichOneofArgType__content: typing_extensions.TypeAlias = typing.Literal[ - "_content", b"_content" - ] - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - _WhichOneofReturnType__name: typing_extensions.TypeAlias = typing.Literal["name"] - _WhichOneofArgType__name: typing_extensions.TypeAlias = typing.Literal["_name", b"_name"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__change_note - ) -> _WhichOneofReturnType__change_note | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__content - ) -> _WhichOneofReturnType__content | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__name - ) -> _WhichOneofReturnType__name | None: ... - -Global___UpdateSummarizationTemplateRequest: typing_extensions.TypeAlias = ( - UpdateSummarizationTemplateRequest -) - -@typing.final -class ArchiveSummarizationTemplateRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_ID_FIELD_NUMBER: builtins.int - template_id: builtins.str - def __init__( - self, - *, - template_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["template_id", b"template_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ArchiveSummarizationTemplateRequest: typing_extensions.TypeAlias = ( - ArchiveSummarizationTemplateRequest -) - -@typing.final -class ListSummarizationTemplateVersionsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_ID_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - template_id: builtins.str - limit: builtins.int - offset: builtins.int - def __init__( - self, - *, - template_id: builtins.str = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "limit", b"limit", "offset", b"offset", "template_id", b"template_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSummarizationTemplateVersionsRequest: typing_extensions.TypeAlias = ( - ListSummarizationTemplateVersionsRequest -) - -@typing.final -class ListSummarizationTemplateVersionsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VERSIONS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - @property - def versions( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___SummarizationTemplateVersionProto - ]: ... - def __init__( - self, - *, - versions: collections.abc.Iterable[Global___SummarizationTemplateVersionProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "total_count", b"total_count", "versions", b"versions" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSummarizationTemplateVersionsResponse: typing_extensions.TypeAlias = ( - ListSummarizationTemplateVersionsResponse -) - -@typing.final -class RestoreSummarizationTemplateVersionRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TEMPLATE_ID_FIELD_NUMBER: builtins.int - VERSION_ID_FIELD_NUMBER: builtins.int - template_id: builtins.str - version_id: builtins.str - def __init__( - self, - *, - template_id: builtins.str = ..., - version_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "template_id", b"template_id", "version_id", b"version_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RestoreSummarizationTemplateVersionRequest: typing_extensions.TypeAlias = ( - RestoreSummarizationTemplateVersionRequest -) - -@typing.final -class ServerInfoRequest(google.protobuf.message.Message): - """============================================================================= - Server Info Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___ServerInfoRequest: typing_extensions.TypeAlias = ServerInfoRequest - -@typing.final -class ServerInfo(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VERSION_FIELD_NUMBER: builtins.int - ASR_MODEL_FIELD_NUMBER: builtins.int - ASR_READY_FIELD_NUMBER: builtins.int - SUPPORTED_SAMPLE_RATES_FIELD_NUMBER: builtins.int - MAX_CHUNK_SIZE_FIELD_NUMBER: builtins.int - UPTIME_SECONDS_FIELD_NUMBER: builtins.int - ACTIVE_MEETINGS_FIELD_NUMBER: builtins.int - DIARIZATION_ENABLED_FIELD_NUMBER: builtins.int - DIARIZATION_READY_FIELD_NUMBER: builtins.int - STATE_VERSION_FIELD_NUMBER: builtins.int - SYSTEM_RAM_TOTAL_BYTES_FIELD_NUMBER: builtins.int - SYSTEM_RAM_AVAILABLE_BYTES_FIELD_NUMBER: builtins.int - GPU_VRAM_TOTAL_BYTES_FIELD_NUMBER: builtins.int - GPU_VRAM_AVAILABLE_BYTES_FIELD_NUMBER: builtins.int - version: builtins.str - """Server version""" - asr_model: builtins.str - """ASR model loaded""" - asr_ready: builtins.bool - """Whether ASR is ready""" - max_chunk_size: builtins.int - """Maximum audio chunk size in bytes""" - uptime_seconds: builtins.float - """Server uptime in seconds""" - active_meetings: builtins.int - """Number of active meetings""" - diarization_enabled: builtins.bool - """Whether diarization is enabled""" - diarization_ready: builtins.bool - """Whether diarization models are ready""" - state_version: builtins.int - """Server state version for cache invalidation (Sprint GAP-002) - Increment when breaking state changes require client cache invalidation - """ - system_ram_total_bytes: builtins.int - """Total system RAM in bytes""" - system_ram_available_bytes: builtins.int - """Available system RAM in bytes""" - gpu_vram_total_bytes: builtins.int - """Total GPU VRAM in bytes (primary device)""" - gpu_vram_available_bytes: builtins.int - """Available GPU VRAM in bytes (primary device)""" - @property - def supported_sample_rates( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Supported sample rates""" - - def __init__( - self, - *, - version: builtins.str = ..., - asr_model: builtins.str = ..., - asr_ready: builtins.bool = ..., - supported_sample_rates: collections.abc.Iterable[builtins.int] | None = ..., - max_chunk_size: builtins.int = ..., - uptime_seconds: builtins.float = ..., - active_meetings: builtins.int = ..., - diarization_enabled: builtins.bool = ..., - diarization_ready: builtins.bool = ..., - state_version: builtins.int = ..., - system_ram_total_bytes: builtins.int | None = ..., - system_ram_available_bytes: builtins.int | None = ..., - gpu_vram_total_bytes: builtins.int | None = ..., - gpu_vram_available_bytes: builtins.int | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_gpu_vram_available_bytes", - b"_gpu_vram_available_bytes", - "_gpu_vram_total_bytes", - b"_gpu_vram_total_bytes", - "_system_ram_available_bytes", - b"_system_ram_available_bytes", - "_system_ram_total_bytes", - b"_system_ram_total_bytes", - "gpu_vram_available_bytes", - b"gpu_vram_available_bytes", - "gpu_vram_total_bytes", - b"gpu_vram_total_bytes", - "system_ram_available_bytes", - b"system_ram_available_bytes", - "system_ram_total_bytes", - b"system_ram_total_bytes", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_gpu_vram_available_bytes", - b"_gpu_vram_available_bytes", - "_gpu_vram_total_bytes", - b"_gpu_vram_total_bytes", - "_system_ram_available_bytes", - b"_system_ram_available_bytes", - "_system_ram_total_bytes", - b"_system_ram_total_bytes", - "active_meetings", - b"active_meetings", - "asr_model", - b"asr_model", - "asr_ready", - b"asr_ready", - "diarization_enabled", - b"diarization_enabled", - "diarization_ready", - b"diarization_ready", - "gpu_vram_available_bytes", - b"gpu_vram_available_bytes", - "gpu_vram_total_bytes", - b"gpu_vram_total_bytes", - "max_chunk_size", - b"max_chunk_size", - "state_version", - b"state_version", - "supported_sample_rates", - b"supported_sample_rates", - "system_ram_available_bytes", - b"system_ram_available_bytes", - "system_ram_total_bytes", - b"system_ram_total_bytes", - "uptime_seconds", - b"uptime_seconds", - "version", - b"version", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__gpu_vram_available_bytes: typing_extensions.TypeAlias = typing.Literal[ - "gpu_vram_available_bytes" - ] - _WhichOneofArgType__gpu_vram_available_bytes: typing_extensions.TypeAlias = typing.Literal[ - "_gpu_vram_available_bytes", b"_gpu_vram_available_bytes" - ] - _WhichOneofReturnType__gpu_vram_total_bytes: typing_extensions.TypeAlias = typing.Literal[ - "gpu_vram_total_bytes" - ] - _WhichOneofArgType__gpu_vram_total_bytes: typing_extensions.TypeAlias = typing.Literal[ - "_gpu_vram_total_bytes", b"_gpu_vram_total_bytes" - ] - _WhichOneofReturnType__system_ram_available_bytes: typing_extensions.TypeAlias = typing.Literal[ - "system_ram_available_bytes" - ] - _WhichOneofArgType__system_ram_available_bytes: typing_extensions.TypeAlias = typing.Literal[ - "_system_ram_available_bytes", b"_system_ram_available_bytes" - ] - _WhichOneofReturnType__system_ram_total_bytes: typing_extensions.TypeAlias = typing.Literal[ - "system_ram_total_bytes" - ] - _WhichOneofArgType__system_ram_total_bytes: typing_extensions.TypeAlias = typing.Literal[ - "_system_ram_total_bytes", b"_system_ram_total_bytes" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__gpu_vram_available_bytes - ) -> _WhichOneofReturnType__gpu_vram_available_bytes | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__gpu_vram_total_bytes - ) -> _WhichOneofReturnType__gpu_vram_total_bytes | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__system_ram_available_bytes - ) -> _WhichOneofReturnType__system_ram_available_bytes | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__system_ram_total_bytes - ) -> _WhichOneofReturnType__system_ram_total_bytes | None: ... - -Global___ServerInfo: typing_extensions.TypeAlias = ServerInfo - -@typing.final -class AsrConfiguration(google.protobuf.message.Message): - """Current ASR configuration and capabilities""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MODEL_SIZE_FIELD_NUMBER: builtins.int - DEVICE_FIELD_NUMBER: builtins.int - COMPUTE_TYPE_FIELD_NUMBER: builtins.int - IS_READY_FIELD_NUMBER: builtins.int - CUDA_AVAILABLE_FIELD_NUMBER: builtins.int - AVAILABLE_MODEL_SIZES_FIELD_NUMBER: builtins.int - AVAILABLE_COMPUTE_TYPES_FIELD_NUMBER: builtins.int - ROCM_AVAILABLE_FIELD_NUMBER: builtins.int - GPU_BACKEND_FIELD_NUMBER: builtins.int - model_size: builtins.str - """Currently loaded model size (e.g., "base", "small", "medium")""" - device: Global___AsrDevice.ValueType - """Current device in use""" - compute_type: Global___AsrComputeType.ValueType - """Current compute type""" - is_ready: builtins.bool - """Whether ASR engine is ready for transcription""" - cuda_available: builtins.bool - """Whether CUDA is available on this server""" - rocm_available: builtins.bool - """Whether ROCm is available on this server""" - gpu_backend: builtins.str - """Current GPU backend (none, cuda, rocm, mps)""" - @property - def available_model_sizes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Available model sizes that can be loaded""" - - @property - def available_compute_types( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[ - Global___AsrComputeType.ValueType - ]: - """Available compute types for current device""" - - def __init__( - self, - *, - model_size: builtins.str = ..., - device: Global___AsrDevice.ValueType = ..., - compute_type: Global___AsrComputeType.ValueType = ..., - is_ready: builtins.bool = ..., - cuda_available: builtins.bool = ..., - available_model_sizes: collections.abc.Iterable[builtins.str] | None = ..., - available_compute_types: collections.abc.Iterable[Global___AsrComputeType.ValueType] - | None = ..., - rocm_available: builtins.bool = ..., - gpu_backend: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "available_compute_types", - b"available_compute_types", - "available_model_sizes", - b"available_model_sizes", - "compute_type", - b"compute_type", - "cuda_available", - b"cuda_available", - "device", - b"device", - "gpu_backend", - b"gpu_backend", - "is_ready", - b"is_ready", - "model_size", - b"model_size", - "rocm_available", - b"rocm_available", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___AsrConfiguration: typing_extensions.TypeAlias = AsrConfiguration - -@typing.final -class GetAsrConfigurationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetAsrConfigurationRequest: typing_extensions.TypeAlias = GetAsrConfigurationRequest - -@typing.final -class GetAsrConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONFIGURATION_FIELD_NUMBER: builtins.int - @property - def configuration(self) -> Global___AsrConfiguration: ... - def __init__( - self, - *, - configuration: Global___AsrConfiguration | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetAsrConfigurationResponse: typing_extensions.TypeAlias = GetAsrConfigurationResponse - -@typing.final -class UpdateAsrConfigurationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MODEL_SIZE_FIELD_NUMBER: builtins.int - DEVICE_FIELD_NUMBER: builtins.int - COMPUTE_TYPE_FIELD_NUMBER: builtins.int - model_size: builtins.str - """New model size to load (optional, keeps current if empty)""" - device: Global___AsrDevice.ValueType - """New device (optional, keeps current if unspecified)""" - compute_type: Global___AsrComputeType.ValueType - """New compute type (optional, keeps current if unspecified)""" - def __init__( - self, - *, - model_size: builtins.str | None = ..., - device: Global___AsrDevice.ValueType | None = ..., - compute_type: Global___AsrComputeType.ValueType | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_compute_type", - b"_compute_type", - "_device", - b"_device", - "_model_size", - b"_model_size", - "compute_type", - b"compute_type", - "device", - b"device", - "model_size", - b"model_size", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_compute_type", - b"_compute_type", - "_device", - b"_device", - "_model_size", - b"_model_size", - "compute_type", - b"compute_type", - "device", - b"device", - "model_size", - b"model_size", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__compute_type: typing_extensions.TypeAlias = typing.Literal[ - "compute_type" - ] - _WhichOneofArgType__compute_type: typing_extensions.TypeAlias = typing.Literal[ - "_compute_type", b"_compute_type" - ] - _WhichOneofReturnType__device: typing_extensions.TypeAlias = typing.Literal["device"] - _WhichOneofArgType__device: typing_extensions.TypeAlias = typing.Literal["_device", b"_device"] - _WhichOneofReturnType__model_size: typing_extensions.TypeAlias = typing.Literal["model_size"] - _WhichOneofArgType__model_size: typing_extensions.TypeAlias = typing.Literal[ - "_model_size", b"_model_size" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__compute_type - ) -> _WhichOneofReturnType__compute_type | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__device - ) -> _WhichOneofReturnType__device | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__model_size - ) -> _WhichOneofReturnType__model_size | None: ... - -Global___UpdateAsrConfigurationRequest: typing_extensions.TypeAlias = UpdateAsrConfigurationRequest - -@typing.final -class UpdateAsrConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - ACCEPTED_FIELD_NUMBER: builtins.int - job_id: builtins.str - """Background job identifier for tracking reload progress""" - status: Global___JobStatus.ValueType - """Initial status (always QUEUED or RUNNING)""" - error_message: builtins.str - """Error message if validation failed before job creation""" - accepted: builtins.bool - """Whether the request was accepted (false if active recording)""" - def __init__( - self, - *, - job_id: builtins.str = ..., - status: Global___JobStatus.ValueType = ..., - error_message: builtins.str = ..., - accepted: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "accepted", - b"accepted", - "error_message", - b"error_message", - "job_id", - b"job_id", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateAsrConfigurationResponse: typing_extensions.TypeAlias = ( - UpdateAsrConfigurationResponse -) - -@typing.final -class GetAsrConfigurationJobStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - job_id: builtins.str - def __init__( - self, - *, - job_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["job_id", b"job_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetAsrConfigurationJobStatusRequest: typing_extensions.TypeAlias = ( - GetAsrConfigurationJobStatusRequest -) - -@typing.final -class AsrConfigurationJobStatus(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - PROGRESS_PERCENT_FIELD_NUMBER: builtins.int - PHASE_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - NEW_CONFIGURATION_FIELD_NUMBER: builtins.int - job_id: builtins.str - status: Global___JobStatus.ValueType - """Current status""" - progress_percent: builtins.float - """Progress percentage (0.0-100.0), primarily for model download""" - phase: builtins.str - """Current phase: "validating", "downloading", "loading", "completed" """ - error_message: builtins.str - """Error message if failed""" - @property - def new_configuration(self) -> Global___AsrConfiguration: - """New configuration after successful reload""" - - def __init__( - self, - *, - job_id: builtins.str = ..., - status: Global___JobStatus.ValueType = ..., - progress_percent: builtins.float = ..., - phase: builtins.str = ..., - error_message: builtins.str = ..., - new_configuration: Global___AsrConfiguration | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_new_configuration", b"_new_configuration", "new_configuration", b"new_configuration" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_new_configuration", - b"_new_configuration", - "error_message", - b"error_message", - "job_id", - b"job_id", - "new_configuration", - b"new_configuration", - "phase", - b"phase", - "progress_percent", - b"progress_percent", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__new_configuration: typing_extensions.TypeAlias = typing.Literal[ - "new_configuration" - ] - _WhichOneofArgType__new_configuration: typing_extensions.TypeAlias = typing.Literal[ - "_new_configuration", b"_new_configuration" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__new_configuration - ) -> _WhichOneofReturnType__new_configuration | None: ... - -Global___AsrConfigurationJobStatus: typing_extensions.TypeAlias = AsrConfigurationJobStatus - -@typing.final -class StreamingConfiguration(google.protobuf.message.Message): - """============================================================================= - Streaming Configuration Messages (Sprint 20) - ============================================================================= - - Streaming configuration for partials and segmentation - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_CADENCE_SECONDS_FIELD_NUMBER: builtins.int - MIN_PARTIAL_AUDIO_SECONDS_FIELD_NUMBER: builtins.int - MAX_SEGMENT_DURATION_SECONDS_FIELD_NUMBER: builtins.int - MIN_SPEECH_DURATION_SECONDS_FIELD_NUMBER: builtins.int - TRAILING_SILENCE_SECONDS_FIELD_NUMBER: builtins.int - LEADING_BUFFER_SECONDS_FIELD_NUMBER: builtins.int - partial_cadence_seconds: builtins.float - """Interval for emitting partial transcripts (seconds)""" - min_partial_audio_seconds: builtins.float - """Minimum audio duration required to emit a partial (seconds)""" - max_segment_duration_seconds: builtins.float - """Maximum duration before forcing a segment split (seconds)""" - min_speech_duration_seconds: builtins.float - """Minimum speech duration to keep a segment (seconds)""" - trailing_silence_seconds: builtins.float - """Trailing silence to include after speech ends (seconds)""" - leading_buffer_seconds: builtins.float - """Leading buffer to include before speech starts (seconds)""" - def __init__( - self, - *, - partial_cadence_seconds: builtins.float = ..., - min_partial_audio_seconds: builtins.float = ..., - max_segment_duration_seconds: builtins.float = ..., - min_speech_duration_seconds: builtins.float = ..., - trailing_silence_seconds: builtins.float = ..., - leading_buffer_seconds: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "leading_buffer_seconds", - b"leading_buffer_seconds", - "max_segment_duration_seconds", - b"max_segment_duration_seconds", - "min_partial_audio_seconds", - b"min_partial_audio_seconds", - "min_speech_duration_seconds", - b"min_speech_duration_seconds", - "partial_cadence_seconds", - b"partial_cadence_seconds", - "trailing_silence_seconds", - b"trailing_silence_seconds", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___StreamingConfiguration: typing_extensions.TypeAlias = StreamingConfiguration - -@typing.final -class GetStreamingConfigurationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetStreamingConfigurationRequest: typing_extensions.TypeAlias = ( - GetStreamingConfigurationRequest -) - -@typing.final -class GetStreamingConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONFIGURATION_FIELD_NUMBER: builtins.int - @property - def configuration(self) -> Global___StreamingConfiguration: ... - def __init__( - self, - *, - configuration: Global___StreamingConfiguration | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetStreamingConfigurationResponse: typing_extensions.TypeAlias = ( - GetStreamingConfigurationResponse -) - -@typing.final -class UpdateStreamingConfigurationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_CADENCE_SECONDS_FIELD_NUMBER: builtins.int - MIN_PARTIAL_AUDIO_SECONDS_FIELD_NUMBER: builtins.int - MAX_SEGMENT_DURATION_SECONDS_FIELD_NUMBER: builtins.int - MIN_SPEECH_DURATION_SECONDS_FIELD_NUMBER: builtins.int - TRAILING_SILENCE_SECONDS_FIELD_NUMBER: builtins.int - LEADING_BUFFER_SECONDS_FIELD_NUMBER: builtins.int - partial_cadence_seconds: builtins.float - """Interval for emitting partial transcripts (seconds)""" - min_partial_audio_seconds: builtins.float - """Minimum audio duration required to emit a partial (seconds)""" - max_segment_duration_seconds: builtins.float - """Maximum duration before forcing a segment split (seconds)""" - min_speech_duration_seconds: builtins.float - """Minimum speech duration to keep a segment (seconds)""" - trailing_silence_seconds: builtins.float - """Trailing silence to include after speech ends (seconds)""" - leading_buffer_seconds: builtins.float - """Leading buffer to include before speech starts (seconds)""" - def __init__( - self, - *, - partial_cadence_seconds: builtins.float | None = ..., - min_partial_audio_seconds: builtins.float | None = ..., - max_segment_duration_seconds: builtins.float | None = ..., - min_speech_duration_seconds: builtins.float | None = ..., - trailing_silence_seconds: builtins.float | None = ..., - leading_buffer_seconds: builtins.float | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_leading_buffer_seconds", - b"_leading_buffer_seconds", - "_max_segment_duration_seconds", - b"_max_segment_duration_seconds", - "_min_partial_audio_seconds", - b"_min_partial_audio_seconds", - "_min_speech_duration_seconds", - b"_min_speech_duration_seconds", - "_partial_cadence_seconds", - b"_partial_cadence_seconds", - "_trailing_silence_seconds", - b"_trailing_silence_seconds", - "leading_buffer_seconds", - b"leading_buffer_seconds", - "max_segment_duration_seconds", - b"max_segment_duration_seconds", - "min_partial_audio_seconds", - b"min_partial_audio_seconds", - "min_speech_duration_seconds", - b"min_speech_duration_seconds", - "partial_cadence_seconds", - b"partial_cadence_seconds", - "trailing_silence_seconds", - b"trailing_silence_seconds", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_leading_buffer_seconds", - b"_leading_buffer_seconds", - "_max_segment_duration_seconds", - b"_max_segment_duration_seconds", - "_min_partial_audio_seconds", - b"_min_partial_audio_seconds", - "_min_speech_duration_seconds", - b"_min_speech_duration_seconds", - "_partial_cadence_seconds", - b"_partial_cadence_seconds", - "_trailing_silence_seconds", - b"_trailing_silence_seconds", - "leading_buffer_seconds", - b"leading_buffer_seconds", - "max_segment_duration_seconds", - b"max_segment_duration_seconds", - "min_partial_audio_seconds", - b"min_partial_audio_seconds", - "min_speech_duration_seconds", - b"min_speech_duration_seconds", - "partial_cadence_seconds", - b"partial_cadence_seconds", - "trailing_silence_seconds", - b"trailing_silence_seconds", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__leading_buffer_seconds: typing_extensions.TypeAlias = typing.Literal[ - "leading_buffer_seconds" - ] - _WhichOneofArgType__leading_buffer_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_leading_buffer_seconds", b"_leading_buffer_seconds" - ] - _WhichOneofReturnType__max_segment_duration_seconds: typing_extensions.TypeAlias = ( - typing.Literal["max_segment_duration_seconds"] - ) - _WhichOneofArgType__max_segment_duration_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_max_segment_duration_seconds", b"_max_segment_duration_seconds" - ] - _WhichOneofReturnType__min_partial_audio_seconds: typing_extensions.TypeAlias = typing.Literal[ - "min_partial_audio_seconds" - ] - _WhichOneofArgType__min_partial_audio_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_min_partial_audio_seconds", b"_min_partial_audio_seconds" - ] - _WhichOneofReturnType__min_speech_duration_seconds: typing_extensions.TypeAlias = ( - typing.Literal["min_speech_duration_seconds"] - ) - _WhichOneofArgType__min_speech_duration_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_min_speech_duration_seconds", b"_min_speech_duration_seconds" - ] - _WhichOneofReturnType__partial_cadence_seconds: typing_extensions.TypeAlias = typing.Literal[ - "partial_cadence_seconds" - ] - _WhichOneofArgType__partial_cadence_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_partial_cadence_seconds", b"_partial_cadence_seconds" - ] - _WhichOneofReturnType__trailing_silence_seconds: typing_extensions.TypeAlias = typing.Literal[ - "trailing_silence_seconds" - ] - _WhichOneofArgType__trailing_silence_seconds: typing_extensions.TypeAlias = typing.Literal[ - "_trailing_silence_seconds", b"_trailing_silence_seconds" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__leading_buffer_seconds - ) -> _WhichOneofReturnType__leading_buffer_seconds | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__max_segment_duration_seconds - ) -> _WhichOneofReturnType__max_segment_duration_seconds | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__min_partial_audio_seconds - ) -> _WhichOneofReturnType__min_partial_audio_seconds | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__min_speech_duration_seconds - ) -> _WhichOneofReturnType__min_speech_duration_seconds | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__partial_cadence_seconds - ) -> _WhichOneofReturnType__partial_cadence_seconds | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__trailing_silence_seconds - ) -> _WhichOneofReturnType__trailing_silence_seconds | None: ... - -Global___UpdateStreamingConfigurationRequest: typing_extensions.TypeAlias = ( - UpdateStreamingConfigurationRequest -) - -@typing.final -class UpdateStreamingConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONFIGURATION_FIELD_NUMBER: builtins.int - @property - def configuration(self) -> Global___StreamingConfiguration: ... - def __init__( - self, - *, - configuration: Global___StreamingConfiguration | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "configuration", b"configuration" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateStreamingConfigurationResponse: typing_extensions.TypeAlias = ( - UpdateStreamingConfigurationResponse -) - -@typing.final -class Annotation(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - MEETING_ID_FIELD_NUMBER: builtins.int - ANNOTATION_TYPE_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique annotation identifier""" - meeting_id: builtins.str - """Meeting this annotation belongs to""" - annotation_type: Global___AnnotationType.ValueType - """Type of annotation""" - text: builtins.str - """Annotation text""" - start_time: builtins.float - """Start time relative to meeting start (seconds)""" - end_time: builtins.float - """End time relative to meeting start (seconds)""" - created_at: builtins.float - """Creation timestamp (Unix epoch seconds)""" - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Linked segment IDs (evidence linking)""" - - def __init__( - self, - *, - id: builtins.str = ..., - meeting_id: builtins.str = ..., - annotation_type: Global___AnnotationType.ValueType = ..., - text: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - created_at: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "annotation_type", - b"annotation_type", - "created_at", - b"created_at", - "end_time", - b"end_time", - "id", - b"id", - "meeting_id", - b"meeting_id", - "segment_ids", - b"segment_ids", - "start_time", - b"start_time", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___Annotation: typing_extensions.TypeAlias = Annotation - -@typing.final -class AddAnnotationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - ANNOTATION_TYPE_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID to add annotation to""" - annotation_type: Global___AnnotationType.ValueType - """Type of annotation""" - text: builtins.str - """Annotation text""" - start_time: builtins.float - """Start time relative to meeting start (seconds)""" - end_time: builtins.float - """End time relative to meeting start (seconds)""" - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Optional linked segment IDs""" - - def __init__( - self, - *, - meeting_id: builtins.str = ..., - annotation_type: Global___AnnotationType.ValueType = ..., - text: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "annotation_type", - b"annotation_type", - "end_time", - b"end_time", - "meeting_id", - b"meeting_id", - "segment_ids", - b"segment_ids", - "start_time", - b"start_time", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___AddAnnotationRequest: typing_extensions.TypeAlias = AddAnnotationRequest - -@typing.final -class GetAnnotationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ANNOTATION_ID_FIELD_NUMBER: builtins.int - annotation_id: builtins.str - def __init__( - self, - *, - annotation_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "annotation_id", b"annotation_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetAnnotationRequest: typing_extensions.TypeAlias = GetAnnotationRequest - -@typing.final -class ListAnnotationsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID to list annotations for""" - start_time: builtins.float - """Optional time range filter""" - end_time: builtins.float - def __init__( - self, - *, - meeting_id: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "end_time", b"end_time", "meeting_id", b"meeting_id", "start_time", b"start_time" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListAnnotationsRequest: typing_extensions.TypeAlias = ListAnnotationsRequest - -@typing.final -class ListAnnotationsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ANNOTATIONS_FIELD_NUMBER: builtins.int - @property - def annotations( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___Annotation - ]: ... - def __init__( - self, - *, - annotations: collections.abc.Iterable[Global___Annotation] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["annotations", b"annotations"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListAnnotationsResponse: typing_extensions.TypeAlias = ListAnnotationsResponse - -@typing.final -class UpdateAnnotationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ANNOTATION_ID_FIELD_NUMBER: builtins.int - ANNOTATION_TYPE_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - annotation_id: builtins.str - """Annotation ID to update""" - annotation_type: Global___AnnotationType.ValueType - """Updated type (optional, keeps existing if not set)""" - text: builtins.str - """Updated text (optional, keeps existing if empty)""" - start_time: builtins.float - """Updated start time (optional, keeps existing if 0)""" - end_time: builtins.float - """Updated end time (optional, keeps existing if 0)""" - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Updated segment IDs (replaces existing)""" - - def __init__( - self, - *, - annotation_id: builtins.str = ..., - annotation_type: Global___AnnotationType.ValueType = ..., - text: builtins.str = ..., - start_time: builtins.float = ..., - end_time: builtins.float = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "annotation_id", - b"annotation_id", - "annotation_type", - b"annotation_type", - "end_time", - b"end_time", - "segment_ids", - b"segment_ids", - "start_time", - b"start_time", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateAnnotationRequest: typing_extensions.TypeAlias = UpdateAnnotationRequest - -@typing.final -class DeleteAnnotationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ANNOTATION_ID_FIELD_NUMBER: builtins.int - annotation_id: builtins.str - def __init__( - self, - *, - annotation_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "annotation_id", b"annotation_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteAnnotationRequest: typing_extensions.TypeAlias = DeleteAnnotationRequest - -@typing.final -class DeleteAnnotationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteAnnotationResponse: typing_extensions.TypeAlias = DeleteAnnotationResponse - -@typing.final -class ProcessingStepState(google.protobuf.message.Message): - """State of a single processing step with timing and error info""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATUS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - STARTED_AT_FIELD_NUMBER: builtins.int - COMPLETED_AT_FIELD_NUMBER: builtins.int - status: Global___ProcessingStepStatus.ValueType - """Current status of this step""" - error_message: builtins.str - """Error message if status is FAILED""" - started_at: builtins.float - """When this step started (Unix epoch seconds), 0 if not started""" - completed_at: builtins.float - """When this step completed (Unix epoch seconds), 0 if not completed""" - def __init__( - self, - *, - status: Global___ProcessingStepStatus.ValueType = ..., - error_message: builtins.str = ..., - started_at: builtins.float = ..., - completed_at: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "completed_at", - b"completed_at", - "error_message", - b"error_message", - "started_at", - b"started_at", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ProcessingStepState: typing_extensions.TypeAlias = ProcessingStepState - -@typing.final -class ProcessingStatus(google.protobuf.message.Message): - """Aggregate status of all post-processing steps for a meeting""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUMMARY_FIELD_NUMBER: builtins.int - ENTITIES_FIELD_NUMBER: builtins.int - DIARIZATION_FIELD_NUMBER: builtins.int - @property - def summary(self) -> Global___ProcessingStepState: - """Summary generation status""" - - @property - def entities(self) -> Global___ProcessingStepState: - """Entity extraction status""" - - @property - def diarization(self) -> Global___ProcessingStepState: - """Speaker diarization status""" - - def __init__( - self, - *, - summary: Global___ProcessingStepState | None = ..., - entities: Global___ProcessingStepState | None = ..., - diarization: Global___ProcessingStepState | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "diarization", b"diarization", "entities", b"entities", "summary", b"summary" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "diarization", b"diarization", "entities", b"entities", "summary", b"summary" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ProcessingStatus: typing_extensions.TypeAlias = ProcessingStatus - -@typing.final -class ExportTranscriptRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - FORMAT_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID to export""" - format: Global___ExportFormat.ValueType - """Export format""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - format: Global___ExportFormat.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "format", b"format", "meeting_id", b"meeting_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ExportTranscriptRequest: typing_extensions.TypeAlias = ExportTranscriptRequest - -@typing.final -class ExportTranscriptResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONTENT_FIELD_NUMBER: builtins.int - FORMAT_NAME_FIELD_NUMBER: builtins.int - FILE_EXTENSION_FIELD_NUMBER: builtins.int - content: builtins.str - """Exported content""" - format_name: builtins.str - """Format name""" - file_extension: builtins.str - """Suggested file extension""" - def __init__( - self, - *, - content: builtins.str = ..., - format_name: builtins.str = ..., - file_extension: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "content", b"content", "file_extension", b"file_extension", "format_name", b"format_name" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ExportTranscriptResponse: typing_extensions.TypeAlias = ExportTranscriptResponse - -@typing.final -class RefineSpeakerDiarizationRequest(google.protobuf.message.Message): - """============================================================================= - Speaker Diarization Messages - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - NUM_SPEAKERS_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID to run diarization on""" - num_speakers: builtins.int - """Optional known number of speakers (auto-detect if not set or 0)""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - num_speakers: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "meeting_id", b"meeting_id", "num_speakers", b"num_speakers" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RefineSpeakerDiarizationRequest: typing_extensions.TypeAlias = ( - RefineSpeakerDiarizationRequest -) - -@typing.final -class RefineSpeakerDiarizationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SEGMENTS_UPDATED_FIELD_NUMBER: builtins.int - SPEAKER_IDS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - JOB_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - segments_updated: builtins.int - """Number of segments updated with speaker labels""" - error_message: builtins.str - """Error message if diarization failed""" - job_id: builtins.str - """Background job identifier (empty if request failed)""" - status: Global___JobStatus.ValueType - """Current job status""" - @property - def speaker_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Distinct speaker IDs found""" - - def __init__( - self, - *, - segments_updated: builtins.int = ..., - speaker_ids: collections.abc.Iterable[builtins.str] | None = ..., - error_message: builtins.str = ..., - job_id: builtins.str = ..., - status: Global___JobStatus.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", - b"error_message", - "job_id", - b"job_id", - "segments_updated", - b"segments_updated", - "speaker_ids", - b"speaker_ids", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RefineSpeakerDiarizationResponse: typing_extensions.TypeAlias = ( - RefineSpeakerDiarizationResponse -) - -@typing.final -class RenameSpeakerRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - OLD_SPEAKER_ID_FIELD_NUMBER: builtins.int - NEW_SPEAKER_NAME_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID""" - old_speaker_id: builtins.str - """Original speaker ID (e.g., "SPEAKER_00")""" - new_speaker_name: builtins.str - """New speaker name (e.g., "Alice")""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - old_speaker_id: builtins.str = ..., - new_speaker_name: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "meeting_id", - b"meeting_id", - "new_speaker_name", - b"new_speaker_name", - "old_speaker_id", - b"old_speaker_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RenameSpeakerRequest: typing_extensions.TypeAlias = RenameSpeakerRequest - -@typing.final -class RenameSpeakerResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SEGMENTS_UPDATED_FIELD_NUMBER: builtins.int - SUCCESS_FIELD_NUMBER: builtins.int - segments_updated: builtins.int - """Number of segments updated""" - success: builtins.bool - """Success flag""" - def __init__( - self, - *, - segments_updated: builtins.int = ..., - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "segments_updated", b"segments_updated", "success", b"success" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RenameSpeakerResponse: typing_extensions.TypeAlias = RenameSpeakerResponse - -@typing.final -class GetDiarizationJobStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - job_id: builtins.str - """Job ID returned by RefineSpeakerDiarization""" - def __init__( - self, - *, - job_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["job_id", b"job_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetDiarizationJobStatusRequest: typing_extensions.TypeAlias = ( - GetDiarizationJobStatusRequest -) - -@typing.final -class DiarizationJobStatus(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - SEGMENTS_UPDATED_FIELD_NUMBER: builtins.int - SPEAKER_IDS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - PROGRESS_PERCENT_FIELD_NUMBER: builtins.int - job_id: builtins.str - """Job ID""" - status: Global___JobStatus.ValueType - """Current status""" - segments_updated: builtins.int - """Number of segments updated (when completed)""" - error_message: builtins.str - """Error message if failed""" - progress_percent: builtins.float - """Progress percentage (0.0-100.0)""" - @property - def speaker_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Distinct speaker IDs found (when completed)""" - - def __init__( - self, - *, - job_id: builtins.str = ..., - status: Global___JobStatus.ValueType = ..., - segments_updated: builtins.int = ..., - speaker_ids: collections.abc.Iterable[builtins.str] | None = ..., - error_message: builtins.str = ..., - progress_percent: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", - b"error_message", - "job_id", - b"job_id", - "progress_percent", - b"progress_percent", - "segments_updated", - b"segments_updated", - "speaker_ids", - b"speaker_ids", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DiarizationJobStatus: typing_extensions.TypeAlias = DiarizationJobStatus - -@typing.final -class CancelDiarizationJobRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_ID_FIELD_NUMBER: builtins.int - job_id: builtins.str - """Job ID to cancel""" - def __init__( - self, - *, - job_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["job_id", b"job_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CancelDiarizationJobRequest: typing_extensions.TypeAlias = CancelDiarizationJobRequest - -@typing.final -class CancelDiarizationJobResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether cancellation succeeded""" - error_message: builtins.str - """Error message if failed""" - status: Global___JobStatus.ValueType - """Final job status""" - def __init__( - self, - *, - success: builtins.bool = ..., - error_message: builtins.str = ..., - status: Global___JobStatus.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", b"error_message", "status", b"status", "success", b"success" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CancelDiarizationJobResponse: typing_extensions.TypeAlias = CancelDiarizationJobResponse - -@typing.final -class GetActiveDiarizationJobsRequest(google.protobuf.message.Message): - """Empty - returns all active jobs for the current user/session""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetActiveDiarizationJobsRequest: typing_extensions.TypeAlias = ( - GetActiveDiarizationJobsRequest -) - -@typing.final -class GetActiveDiarizationJobsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOBS_FIELD_NUMBER: builtins.int - @property - def jobs( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___DiarizationJobStatus - ]: - """List of active (QUEUED or RUNNING) diarization jobs""" - - def __init__( - self, - *, - jobs: collections.abc.Iterable[Global___DiarizationJobStatus] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["jobs", b"jobs"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetActiveDiarizationJobsResponse: typing_extensions.TypeAlias = ( - GetActiveDiarizationJobsResponse -) - -@typing.final -class ExtractEntitiesRequest(google.protobuf.message.Message): - """============================================================================= - Named Entity Extraction Messages (Sprint 4) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - FORCE_REFRESH_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID to extract entities from""" - force_refresh: builtins.bool - """Force re-extraction even if entities exist""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - force_refresh: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "force_refresh", b"force_refresh", "meeting_id", b"meeting_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ExtractEntitiesRequest: typing_extensions.TypeAlias = ExtractEntitiesRequest - -@typing.final -class ExtractedEntity(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - CATEGORY_FIELD_NUMBER: builtins.int - SEGMENT_IDS_FIELD_NUMBER: builtins.int - CONFIDENCE_FIELD_NUMBER: builtins.int - IS_PINNED_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique entity identifier""" - text: builtins.str - """Entity text as it appears in transcript""" - category: builtins.str - """Category: person, company, product, technical, acronym, location, date, other""" - confidence: builtins.float - """Extraction confidence (0.0-1.0)""" - is_pinned: builtins.bool - """User-confirmed (pinned) entity""" - @property - def segment_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Segment IDs where this entity appears""" - - def __init__( - self, - *, - id: builtins.str = ..., - text: builtins.str = ..., - category: builtins.str = ..., - segment_ids: collections.abc.Iterable[builtins.int] | None = ..., - confidence: builtins.float = ..., - is_pinned: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "category", - b"category", - "confidence", - b"confidence", - "id", - b"id", - "is_pinned", - b"is_pinned", - "segment_ids", - b"segment_ids", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ExtractedEntity: typing_extensions.TypeAlias = ExtractedEntity - -@typing.final -class ExtractEntitiesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENTITIES_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - CACHED_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total entity count""" - cached: builtins.bool - """True if returning cached results""" - @property - def entities( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___ExtractedEntity - ]: - """Extracted entities""" - - def __init__( - self, - *, - entities: collections.abc.Iterable[Global___ExtractedEntity] | None = ..., - total_count: builtins.int = ..., - cached: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "cached", b"cached", "entities", b"entities", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ExtractEntitiesResponse: typing_extensions.TypeAlias = ExtractEntitiesResponse - -@typing.final -class UpdateEntityRequest(google.protobuf.message.Message): - """Entity mutation messages (Sprint 8)""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - ENTITY_ID_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - CATEGORY_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID containing the entity""" - entity_id: builtins.str - """Entity ID to update""" - text: builtins.str - """New text value (optional, empty = no change)""" - category: builtins.str - """New category value (optional, empty = no change)""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - entity_id: builtins.str = ..., - text: builtins.str = ..., - category: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "category", - b"category", - "entity_id", - b"entity_id", - "meeting_id", - b"meeting_id", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateEntityRequest: typing_extensions.TypeAlias = UpdateEntityRequest - -@typing.final -class UpdateEntityResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENTITY_FIELD_NUMBER: builtins.int - @property - def entity(self) -> Global___ExtractedEntity: - """Updated entity""" - - def __init__( - self, - *, - entity: Global___ExtractedEntity | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["entity", b"entity"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["entity", b"entity"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateEntityResponse: typing_extensions.TypeAlias = UpdateEntityResponse - -@typing.final -class DeleteEntityRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEETING_ID_FIELD_NUMBER: builtins.int - ENTITY_ID_FIELD_NUMBER: builtins.int - meeting_id: builtins.str - """Meeting ID containing the entity""" - entity_id: builtins.str - """Entity ID to delete""" - def __init__( - self, - *, - meeting_id: builtins.str = ..., - entity_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "entity_id", b"entity_id", "meeting_id", b"meeting_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteEntityRequest: typing_extensions.TypeAlias = DeleteEntityRequest - -@typing.final -class DeleteEntityResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - """True if entity was deleted""" - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteEntityResponse: typing_extensions.TypeAlias = DeleteEntityResponse - -@typing.final -class CalendarEvent(google.protobuf.message.Message): - """============================================================================= - Calendar Integration Messages (Sprint 5) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - TITLE_FIELD_NUMBER: builtins.int - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - ATTENDEES_FIELD_NUMBER: builtins.int - LOCATION_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - MEETING_URL_FIELD_NUMBER: builtins.int - IS_RECURRING_FIELD_NUMBER: builtins.int - PROVIDER_FIELD_NUMBER: builtins.int - id: builtins.str - """Calendar event identifier""" - title: builtins.str - """Event title""" - start_time: builtins.int - """Start time (Unix timestamp seconds)""" - end_time: builtins.int - """End time (Unix timestamp seconds)""" - location: builtins.str - """Event location""" - description: builtins.str - """Event description""" - meeting_url: builtins.str - """Meeting URL (Zoom, Meet, Teams, etc.)""" - is_recurring: builtins.bool - """Whether event is recurring""" - provider: builtins.str - """Calendar provider: google, outlook""" - @property - def attendees( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Attendee email addresses""" - - def __init__( - self, - *, - id: builtins.str = ..., - title: builtins.str = ..., - start_time: builtins.int = ..., - end_time: builtins.int = ..., - attendees: collections.abc.Iterable[builtins.str] | None = ..., - location: builtins.str = ..., - description: builtins.str = ..., - meeting_url: builtins.str = ..., - is_recurring: builtins.bool = ..., - provider: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "attendees", - b"attendees", - "description", - b"description", - "end_time", - b"end_time", - "id", - b"id", - "is_recurring", - b"is_recurring", - "location", - b"location", - "meeting_url", - b"meeting_url", - "provider", - b"provider", - "start_time", - b"start_time", - "title", - b"title", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CalendarEvent: typing_extensions.TypeAlias = CalendarEvent - -@typing.final -class ListCalendarEventsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - HOURS_AHEAD_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - PROVIDER_FIELD_NUMBER: builtins.int - hours_ahead: builtins.int - """How far ahead to look in hours (default: 24)""" - limit: builtins.int - """Maximum events to return (default: 10)""" - provider: builtins.str - """Optional: specific provider name""" - def __init__( - self, - *, - hours_ahead: builtins.int = ..., - limit: builtins.int = ..., - provider: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "hours_ahead", b"hours_ahead", "limit", b"limit", "provider", b"provider" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListCalendarEventsRequest: typing_extensions.TypeAlias = ListCalendarEventsRequest - -@typing.final -class ListCalendarEventsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - EVENTS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total event count""" - @property - def events( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___CalendarEvent - ]: - """Upcoming calendar events""" - - def __init__( - self, - *, - events: collections.abc.Iterable[Global___CalendarEvent] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "events", b"events", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListCalendarEventsResponse: typing_extensions.TypeAlias = ListCalendarEventsResponse - -@typing.final -class GetCalendarProvidersRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetCalendarProvidersRequest: typing_extensions.TypeAlias = GetCalendarProvidersRequest - -@typing.final -class CalendarProvider(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - IS_AUTHENTICATED_FIELD_NUMBER: builtins.int - DISPLAY_NAME_FIELD_NUMBER: builtins.int - name: builtins.str - """Provider name: google, outlook""" - is_authenticated: builtins.bool - """Whether provider is authenticated""" - display_name: builtins.str - """Display name: "Google Calendar", "Microsoft Outlook" """ - def __init__( - self, - *, - name: builtins.str = ..., - is_authenticated: builtins.bool = ..., - display_name: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "display_name", b"display_name", "is_authenticated", b"is_authenticated", "name", b"name" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CalendarProvider: typing_extensions.TypeAlias = CalendarProvider - -@typing.final -class GetCalendarProvidersResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDERS_FIELD_NUMBER: builtins.int - @property - def providers( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___CalendarProvider - ]: - """Available calendar providers""" - - def __init__( - self, - *, - providers: collections.abc.Iterable[Global___CalendarProvider] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["providers", b"providers"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetCalendarProvidersResponse: typing_extensions.TypeAlias = GetCalendarProvidersResponse - -@typing.final -class InitiateOAuthRequest(google.protobuf.message.Message): - """============================================================================= - OAuth Integration Messages (generic for calendar, email, PKM, etc.) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - REDIRECT_URI_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider to authenticate: google, outlook, notion, etc.""" - redirect_uri: builtins.str - """Redirect URI for OAuth callback""" - integration_type: builtins.str - """Integration type: calendar, email, pkm, custom""" - def __init__( - self, - *, - provider: builtins.str = ..., - redirect_uri: builtins.str = ..., - integration_type: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_type", - b"integration_type", - "provider", - b"provider", - "redirect_uri", - b"redirect_uri", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___InitiateOAuthRequest: typing_extensions.TypeAlias = InitiateOAuthRequest - -@typing.final -class InitiateOAuthResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - AUTH_URL_FIELD_NUMBER: builtins.int - STATE_FIELD_NUMBER: builtins.int - auth_url: builtins.str - """Authorization URL to redirect user to""" - state: builtins.str - """CSRF state token for verification""" - def __init__( - self, - *, - auth_url: builtins.str = ..., - state: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "auth_url", b"auth_url", "state", b"state" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___InitiateOAuthResponse: typing_extensions.TypeAlias = InitiateOAuthResponse - -@typing.final -class CompleteOAuthRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - CODE_FIELD_NUMBER: builtins.int - STATE_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider being authenticated""" - code: builtins.str - """Authorization code from OAuth callback""" - state: builtins.str - """CSRF state token for verification""" - def __init__( - self, - *, - provider: builtins.str = ..., - code: builtins.str = ..., - state: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "code", b"code", "provider", b"provider", "state", b"state" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CompleteOAuthRequest: typing_extensions.TypeAlias = CompleteOAuthRequest - -@typing.final -class CompleteOAuthResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - PROVIDER_EMAIL_FIELD_NUMBER: builtins.int - INTEGRATION_ID_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether authentication succeeded""" - error_message: builtins.str - """Error message if failed""" - provider_email: builtins.str - """Email of authenticated account""" - integration_id: builtins.str - """Server-assigned integration ID (UUID string) - use this for sync operations""" - def __init__( - self, - *, - success: builtins.bool = ..., - error_message: builtins.str = ..., - provider_email: builtins.str = ..., - integration_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", - b"error_message", - "integration_id", - b"integration_id", - "provider_email", - b"provider_email", - "success", - b"success", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___CompleteOAuthResponse: typing_extensions.TypeAlias = CompleteOAuthResponse - -@typing.final -class OAuthConnection(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - EMAIL_FIELD_NUMBER: builtins.int - EXPIRES_AT_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider name: google, outlook, notion""" - status: builtins.str - """Connection status: disconnected, connected, error""" - email: builtins.str - """Email of authenticated account""" - expires_at: builtins.int - """Token expiration timestamp (Unix epoch seconds)""" - error_message: builtins.str - """Error message if status is error""" - integration_type: builtins.str - """Integration type: calendar, email, pkm, custom""" - def __init__( - self, - *, - provider: builtins.str = ..., - status: builtins.str = ..., - email: builtins.str = ..., - expires_at: builtins.int = ..., - error_message: builtins.str = ..., - integration_type: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "email", - b"email", - "error_message", - b"error_message", - "expires_at", - b"expires_at", - "integration_type", - b"integration_type", - "provider", - b"provider", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___OAuthConnection: typing_extensions.TypeAlias = OAuthConnection - -@typing.final -class GetOAuthConnectionStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider to check: google, outlook, notion""" - integration_type: builtins.str - """Optional integration type filter""" - def __init__( - self, - *, - provider: builtins.str = ..., - integration_type: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_type", b"integration_type", "provider", b"provider" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetOAuthConnectionStatusRequest: typing_extensions.TypeAlias = ( - GetOAuthConnectionStatusRequest -) - -@typing.final -class GetOAuthConnectionStatusResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONNECTION_FIELD_NUMBER: builtins.int - @property - def connection(self) -> Global___OAuthConnection: - """Connection details""" - - def __init__( - self, - *, - connection: Global___OAuthConnection | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["connection", b"connection"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["connection", b"connection"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetOAuthConnectionStatusResponse: typing_extensions.TypeAlias = ( - GetOAuthConnectionStatusResponse -) - -@typing.final -class DisconnectOAuthRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider to disconnect""" - integration_type: builtins.str - """Optional integration type""" - def __init__( - self, - *, - provider: builtins.str = ..., - integration_type: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_type", b"integration_type", "provider", b"provider" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DisconnectOAuthRequest: typing_extensions.TypeAlias = DisconnectOAuthRequest - -@typing.final -class DisconnectOAuthResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether disconnection succeeded""" - error_message: builtins.str - """Error message if failed""" - def __init__( - self, - *, - success: builtins.bool = ..., - error_message: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", b"error_message", "success", b"success" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DisconnectOAuthResponse: typing_extensions.TypeAlias = DisconnectOAuthResponse - -@typing.final -class OAuthClientConfig(google.protobuf.message.Message): - """OAuth client override configuration""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CLIENT_ID_FIELD_NUMBER: builtins.int - CLIENT_SECRET_FIELD_NUMBER: builtins.int - REDIRECT_URI_FIELD_NUMBER: builtins.int - SCOPES_FIELD_NUMBER: builtins.int - OVERRIDE_ENABLED_FIELD_NUMBER: builtins.int - HAS_CLIENT_SECRET_FIELD_NUMBER: builtins.int - client_id: builtins.str - """OAuth client ID""" - client_secret: builtins.str - """Optional client secret (request only)""" - redirect_uri: builtins.str - """Redirect URI for OAuth callback""" - override_enabled: builtins.bool - """Whether override should be used""" - has_client_secret: builtins.bool - """Whether a client secret is stored (response only)""" - @property - def scopes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """OAuth scopes to request""" - - def __init__( - self, - *, - client_id: builtins.str = ..., - client_secret: builtins.str | None = ..., - redirect_uri: builtins.str = ..., - scopes: collections.abc.Iterable[builtins.str] | None = ..., - override_enabled: builtins.bool = ..., - has_client_secret: builtins.bool = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_client_secret", b"_client_secret", "client_secret", b"client_secret" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_client_secret", - b"_client_secret", - "client_id", - b"client_id", - "client_secret", - b"client_secret", - "has_client_secret", - b"has_client_secret", - "override_enabled", - b"override_enabled", - "redirect_uri", - b"redirect_uri", - "scopes", - b"scopes", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__client_secret: typing_extensions.TypeAlias = typing.Literal[ - "client_secret" - ] - _WhichOneofArgType__client_secret: typing_extensions.TypeAlias = typing.Literal[ - "_client_secret", b"_client_secret" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__client_secret - ) -> _WhichOneofReturnType__client_secret | None: ... - -Global___OAuthClientConfig: typing_extensions.TypeAlias = OAuthClientConfig - -@typing.final -class GetOAuthClientConfigRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider to configure: google, outlook""" - integration_type: builtins.str - """Optional integration type""" - workspace_id: builtins.str - """Optional workspace ID override""" - def __init__( - self, - *, - provider: builtins.str = ..., - integration_type: builtins.str = ..., - workspace_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_type", - b"integration_type", - "provider", - b"provider", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetOAuthClientConfigRequest: typing_extensions.TypeAlias = GetOAuthClientConfigRequest - -@typing.final -class GetOAuthClientConfigResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONFIG_FIELD_NUMBER: builtins.int - @property - def config(self) -> Global___OAuthClientConfig: ... - def __init__( - self, - *, - config: Global___OAuthClientConfig | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["config", b"config"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["config", b"config"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetOAuthClientConfigResponse: typing_extensions.TypeAlias = GetOAuthClientConfigResponse - -@typing.final -class SetOAuthClientConfigRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_FIELD_NUMBER: builtins.int - INTEGRATION_TYPE_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - CONFIG_FIELD_NUMBER: builtins.int - provider: builtins.str - """Provider to configure: google, outlook""" - integration_type: builtins.str - """Optional integration type""" - workspace_id: builtins.str - """Optional workspace ID override""" - @property - def config(self) -> Global___OAuthClientConfig: - """OAuth client configuration""" - - def __init__( - self, - *, - provider: builtins.str = ..., - integration_type: builtins.str = ..., - workspace_id: builtins.str = ..., - config: Global___OAuthClientConfig | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["config", b"config"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "config", - b"config", - "integration_type", - b"integration_type", - "provider", - b"provider", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetOAuthClientConfigRequest: typing_extensions.TypeAlias = SetOAuthClientConfigRequest - -@typing.final -class SetOAuthClientConfigResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetOAuthClientConfigResponse: typing_extensions.TypeAlias = SetOAuthClientConfigResponse - -@typing.final -class RegisterWebhookRequest(google.protobuf.message.Message): - """============================================================================= - Webhook Management Messages (Sprint 6) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - URL_FIELD_NUMBER: builtins.int - EVENTS_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SECRET_FIELD_NUMBER: builtins.int - TIMEOUT_MS_FIELD_NUMBER: builtins.int - MAX_RETRIES_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace this webhook belongs to""" - url: builtins.str - """Target URL for webhook delivery""" - name: builtins.str - """Human-readable webhook name""" - secret: builtins.str - """Optional HMAC signing secret""" - timeout_ms: builtins.int - """Request timeout in milliseconds (default: 10000)""" - max_retries: builtins.int - """Maximum retry attempts (default: 3)""" - @property - def events( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Events to subscribe to: meeting.completed, summary.generated, recording.started, recording.stopped""" - - def __init__( - self, - *, - workspace_id: builtins.str = ..., - url: builtins.str = ..., - events: collections.abc.Iterable[builtins.str] | None = ..., - name: builtins.str = ..., - secret: builtins.str = ..., - timeout_ms: builtins.int = ..., - max_retries: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "events", - b"events", - "max_retries", - b"max_retries", - "name", - b"name", - "secret", - b"secret", - "timeout_ms", - b"timeout_ms", - "url", - b"url", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RegisterWebhookRequest: typing_extensions.TypeAlias = RegisterWebhookRequest - -@typing.final -class WebhookConfigProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - URL_FIELD_NUMBER: builtins.int - EVENTS_FIELD_NUMBER: builtins.int - ENABLED_FIELD_NUMBER: builtins.int - TIMEOUT_MS_FIELD_NUMBER: builtins.int - MAX_RETRIES_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique webhook identifier""" - workspace_id: builtins.str - """Workspace this webhook belongs to""" - name: builtins.str - """Human-readable webhook name""" - url: builtins.str - """Target URL for webhook delivery""" - enabled: builtins.bool - """Whether webhook is enabled""" - timeout_ms: builtins.int - """Request timeout in milliseconds""" - max_retries: builtins.int - """Maximum retry attempts""" - created_at: builtins.int - """Creation timestamp (Unix epoch seconds)""" - updated_at: builtins.int - """Last update timestamp (Unix epoch seconds)""" - @property - def events( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Subscribed event types""" - - def __init__( - self, - *, - id: builtins.str = ..., - workspace_id: builtins.str = ..., - name: builtins.str = ..., - url: builtins.str = ..., - events: collections.abc.Iterable[builtins.str] | None = ..., - enabled: builtins.bool = ..., - timeout_ms: builtins.int = ..., - max_retries: builtins.int = ..., - created_at: builtins.int = ..., - updated_at: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "created_at", - b"created_at", - "enabled", - b"enabled", - "events", - b"events", - "id", - b"id", - "max_retries", - b"max_retries", - "name", - b"name", - "timeout_ms", - b"timeout_ms", - "updated_at", - b"updated_at", - "url", - b"url", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___WebhookConfigProto: typing_extensions.TypeAlias = WebhookConfigProto - -@typing.final -class ListWebhooksRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENABLED_ONLY_FIELD_NUMBER: builtins.int - enabled_only: builtins.bool - """Filter to only enabled webhooks""" - def __init__( - self, - *, - enabled_only: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "enabled_only", b"enabled_only" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListWebhooksRequest: typing_extensions.TypeAlias = ListWebhooksRequest - -@typing.final -class ListWebhooksResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WEBHOOKS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total webhook count""" - @property - def webhooks( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___WebhookConfigProto - ]: - """Registered webhooks""" - - def __init__( - self, - *, - webhooks: collections.abc.Iterable[Global___WebhookConfigProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "total_count", b"total_count", "webhooks", b"webhooks" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListWebhooksResponse: typing_extensions.TypeAlias = ListWebhooksResponse - -@typing.final -class UpdateWebhookRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WEBHOOK_ID_FIELD_NUMBER: builtins.int - URL_FIELD_NUMBER: builtins.int - EVENTS_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SECRET_FIELD_NUMBER: builtins.int - ENABLED_FIELD_NUMBER: builtins.int - TIMEOUT_MS_FIELD_NUMBER: builtins.int - MAX_RETRIES_FIELD_NUMBER: builtins.int - webhook_id: builtins.str - """Webhook ID to update""" - url: builtins.str - """Updated URL (optional)""" - name: builtins.str - """Updated name (optional)""" - secret: builtins.str - """Updated secret (optional)""" - enabled: builtins.bool - """Updated enabled status (optional)""" - timeout_ms: builtins.int - """Updated timeout in milliseconds (optional)""" - max_retries: builtins.int - """Updated max retries (optional)""" - @property - def events( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Updated events (replaces existing)""" - - def __init__( - self, - *, - webhook_id: builtins.str = ..., - url: builtins.str | None = ..., - events: collections.abc.Iterable[builtins.str] | None = ..., - name: builtins.str | None = ..., - secret: builtins.str | None = ..., - enabled: builtins.bool | None = ..., - timeout_ms: builtins.int | None = ..., - max_retries: builtins.int | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_enabled", - b"_enabled", - "_max_retries", - b"_max_retries", - "_name", - b"_name", - "_secret", - b"_secret", - "_timeout_ms", - b"_timeout_ms", - "_url", - b"_url", - "enabled", - b"enabled", - "max_retries", - b"max_retries", - "name", - b"name", - "secret", - b"secret", - "timeout_ms", - b"timeout_ms", - "url", - b"url", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_enabled", - b"_enabled", - "_max_retries", - b"_max_retries", - "_name", - b"_name", - "_secret", - b"_secret", - "_timeout_ms", - b"_timeout_ms", - "_url", - b"_url", - "enabled", - b"enabled", - "events", - b"events", - "max_retries", - b"max_retries", - "name", - b"name", - "secret", - b"secret", - "timeout_ms", - b"timeout_ms", - "url", - b"url", - "webhook_id", - b"webhook_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__enabled: typing_extensions.TypeAlias = typing.Literal["enabled"] - _WhichOneofArgType__enabled: typing_extensions.TypeAlias = typing.Literal[ - "_enabled", b"_enabled" - ] - _WhichOneofReturnType__max_retries: typing_extensions.TypeAlias = typing.Literal["max_retries"] - _WhichOneofArgType__max_retries: typing_extensions.TypeAlias = typing.Literal[ - "_max_retries", b"_max_retries" - ] - _WhichOneofReturnType__name: typing_extensions.TypeAlias = typing.Literal["name"] - _WhichOneofArgType__name: typing_extensions.TypeAlias = typing.Literal["_name", b"_name"] - _WhichOneofReturnType__secret: typing_extensions.TypeAlias = typing.Literal["secret"] - _WhichOneofArgType__secret: typing_extensions.TypeAlias = typing.Literal["_secret", b"_secret"] - _WhichOneofReturnType__timeout_ms: typing_extensions.TypeAlias = typing.Literal["timeout_ms"] - _WhichOneofArgType__timeout_ms: typing_extensions.TypeAlias = typing.Literal[ - "_timeout_ms", b"_timeout_ms" - ] - _WhichOneofReturnType__url: typing_extensions.TypeAlias = typing.Literal["url"] - _WhichOneofArgType__url: typing_extensions.TypeAlias = typing.Literal["_url", b"_url"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__enabled - ) -> _WhichOneofReturnType__enabled | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__max_retries - ) -> _WhichOneofReturnType__max_retries | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__name - ) -> _WhichOneofReturnType__name | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__secret - ) -> _WhichOneofReturnType__secret | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__timeout_ms - ) -> _WhichOneofReturnType__timeout_ms | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__url - ) -> _WhichOneofReturnType__url | None: ... - -Global___UpdateWebhookRequest: typing_extensions.TypeAlias = UpdateWebhookRequest - -@typing.final -class DeleteWebhookRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WEBHOOK_ID_FIELD_NUMBER: builtins.int - webhook_id: builtins.str - """Webhook ID to delete""" - def __init__( - self, - *, - webhook_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["webhook_id", b"webhook_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteWebhookRequest: typing_extensions.TypeAlias = DeleteWebhookRequest - -@typing.final -class DeleteWebhookResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether deletion succeeded""" - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteWebhookResponse: typing_extensions.TypeAlias = DeleteWebhookResponse - -@typing.final -class WebhookDeliveryProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - WEBHOOK_ID_FIELD_NUMBER: builtins.int - EVENT_TYPE_FIELD_NUMBER: builtins.int - STATUS_CODE_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - ATTEMPT_COUNT_FIELD_NUMBER: builtins.int - DURATION_MS_FIELD_NUMBER: builtins.int - DELIVERED_AT_FIELD_NUMBER: builtins.int - SUCCEEDED_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique delivery identifier""" - webhook_id: builtins.str - """Webhook ID this delivery belongs to""" - event_type: builtins.str - """Event type that triggered this delivery""" - status_code: builtins.int - """HTTP status code (0 if no response)""" - error_message: builtins.str - """Error message if delivery failed""" - attempt_count: builtins.int - """Number of delivery attempts""" - duration_ms: builtins.int - """Request duration in milliseconds""" - delivered_at: builtins.int - """Delivery timestamp (Unix epoch seconds)""" - succeeded: builtins.bool - """Whether delivery succeeded""" - def __init__( - self, - *, - id: builtins.str = ..., - webhook_id: builtins.str = ..., - event_type: builtins.str = ..., - status_code: builtins.int = ..., - error_message: builtins.str = ..., - attempt_count: builtins.int = ..., - duration_ms: builtins.int = ..., - delivered_at: builtins.int = ..., - succeeded: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "attempt_count", - b"attempt_count", - "delivered_at", - b"delivered_at", - "duration_ms", - b"duration_ms", - "error_message", - b"error_message", - "event_type", - b"event_type", - "id", - b"id", - "status_code", - b"status_code", - "succeeded", - b"succeeded", - "webhook_id", - b"webhook_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___WebhookDeliveryProto: typing_extensions.TypeAlias = WebhookDeliveryProto - -@typing.final -class GetWebhookDeliveriesRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WEBHOOK_ID_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - webhook_id: builtins.str - """Webhook ID to get deliveries for""" - limit: builtins.int - """Maximum deliveries to return (default: 50, max: 500)""" - def __init__( - self, - *, - webhook_id: builtins.str = ..., - limit: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "limit", b"limit", "webhook_id", b"webhook_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetWebhookDeliveriesRequest: typing_extensions.TypeAlias = GetWebhookDeliveriesRequest - -@typing.final -class GetWebhookDeliveriesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DELIVERIES_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total delivery count""" - @property - def deliveries( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___WebhookDeliveryProto - ]: - """Recent webhook deliveries""" - - def __init__( - self, - *, - deliveries: collections.abc.Iterable[Global___WebhookDeliveryProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "deliveries", b"deliveries", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetWebhookDeliveriesResponse: typing_extensions.TypeAlias = GetWebhookDeliveriesResponse - -@typing.final -class GrantCloudConsentRequest(google.protobuf.message.Message): - """============================================================================= - Cloud Consent Messages (Sprint 7) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GrantCloudConsentRequest: typing_extensions.TypeAlias = GrantCloudConsentRequest - -@typing.final -class GrantCloudConsentResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GrantCloudConsentResponse: typing_extensions.TypeAlias = GrantCloudConsentResponse - -@typing.final -class RevokeCloudConsentRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___RevokeCloudConsentRequest: typing_extensions.TypeAlias = RevokeCloudConsentRequest - -@typing.final -class RevokeCloudConsentResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___RevokeCloudConsentResponse: typing_extensions.TypeAlias = RevokeCloudConsentResponse - -@typing.final -class GetCloudConsentStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetCloudConsentStatusRequest: typing_extensions.TypeAlias = GetCloudConsentStatusRequest - -@typing.final -class GetCloudConsentStatusResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONSENT_GRANTED_FIELD_NUMBER: builtins.int - consent_granted: builtins.bool - """Whether cloud consent is currently granted""" - def __init__( - self, - *, - consent_granted: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "consent_granted", b"consent_granted" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetCloudConsentStatusResponse: typing_extensions.TypeAlias = GetCloudConsentStatusResponse - -@typing.final -class SetHuggingFaceTokenRequest(google.protobuf.message.Message): - """============================================================================= - HuggingFace Token Management Messages (Sprint 19) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TOKEN_FIELD_NUMBER: builtins.int - VALIDATE_FIELD_NUMBER: builtins.int - token: builtins.str - """HuggingFace access token (will be encrypted at rest)""" - validate: builtins.bool - """Whether to validate token against HuggingFace API""" - def __init__( - self, - *, - token: builtins.str = ..., - validate: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "token", b"token", "validate", b"validate" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetHuggingFaceTokenRequest: typing_extensions.TypeAlias = SetHuggingFaceTokenRequest - -@typing.final -class SetHuggingFaceTokenResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - VALID_FIELD_NUMBER: builtins.int - VALIDATION_ERROR_FIELD_NUMBER: builtins.int - USERNAME_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether the token was saved successfully""" - valid: builtins.bool - """Whether the token passed validation (if validate=true)""" - validation_error: builtins.str - """Validation error message if valid=false""" - username: builtins.str - """HuggingFace username associated with token (if validate=true and valid)""" - def __init__( - self, - *, - success: builtins.bool = ..., - valid: builtins.bool | None = ..., - validation_error: builtins.str = ..., - username: builtins.str = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_valid", b"_valid", "valid", b"valid" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_valid", - b"_valid", - "success", - b"success", - "username", - b"username", - "valid", - b"valid", - "validation_error", - b"validation_error", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__valid: typing_extensions.TypeAlias = typing.Literal["valid"] - _WhichOneofArgType__valid: typing_extensions.TypeAlias = typing.Literal["_valid", b"_valid"] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__valid - ) -> _WhichOneofReturnType__valid | None: ... - -Global___SetHuggingFaceTokenResponse: typing_extensions.TypeAlias = SetHuggingFaceTokenResponse - -@typing.final -class GetHuggingFaceTokenStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetHuggingFaceTokenStatusRequest: typing_extensions.TypeAlias = ( - GetHuggingFaceTokenStatusRequest -) - -@typing.final -class GetHuggingFaceTokenStatusResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - IS_CONFIGURED_FIELD_NUMBER: builtins.int - IS_VALIDATED_FIELD_NUMBER: builtins.int - USERNAME_FIELD_NUMBER: builtins.int - VALIDATED_AT_FIELD_NUMBER: builtins.int - is_configured: builtins.bool - """Whether a token is configured""" - is_validated: builtins.bool - """Whether the token has been validated""" - username: builtins.str - """HuggingFace username (if validated)""" - validated_at: builtins.float - """Last validation timestamp (Unix epoch seconds)""" - def __init__( - self, - *, - is_configured: builtins.bool = ..., - is_validated: builtins.bool = ..., - username: builtins.str = ..., - validated_at: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "is_configured", - b"is_configured", - "is_validated", - b"is_validated", - "username", - b"username", - "validated_at", - b"validated_at", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetHuggingFaceTokenStatusResponse: typing_extensions.TypeAlias = ( - GetHuggingFaceTokenStatusResponse -) - -@typing.final -class DeleteHuggingFaceTokenRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___DeleteHuggingFaceTokenRequest: typing_extensions.TypeAlias = DeleteHuggingFaceTokenRequest - -@typing.final -class DeleteHuggingFaceTokenResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteHuggingFaceTokenResponse: typing_extensions.TypeAlias = ( - DeleteHuggingFaceTokenResponse -) - -@typing.final -class ValidateHuggingFaceTokenRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___ValidateHuggingFaceTokenRequest: typing_extensions.TypeAlias = ( - ValidateHuggingFaceTokenRequest -) - -@typing.final -class ValidateHuggingFaceTokenResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VALID_FIELD_NUMBER: builtins.int - USERNAME_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - valid: builtins.bool - username: builtins.str - error_message: builtins.str - def __init__( - self, - *, - valid: builtins.bool = ..., - username: builtins.str = ..., - error_message: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", b"error_message", "username", b"username", "valid", b"valid" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ValidateHuggingFaceTokenResponse: typing_extensions.TypeAlias = ( - ValidateHuggingFaceTokenResponse -) - -@typing.final -class GetPreferencesRequest(google.protobuf.message.Message): - """============================================================================= - User Preferences Sync Messages (Sprint 14) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEYS_FIELD_NUMBER: builtins.int - @property - def keys( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional: filter to specific keys (empty = all)""" - - def __init__( - self, - *, - keys: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["keys", b"keys"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetPreferencesRequest: typing_extensions.TypeAlias = GetPreferencesRequest - -@typing.final -class GetPreferencesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class PreferencesEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - PREFERENCES_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - updated_at: builtins.float - """Server-side last update timestamp (Unix epoch seconds)""" - etag: builtins.str - """ETag for optimistic concurrency control""" - @property - def preferences( - self, - ) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """All preference key-value pairs as JSON strings - Key: preference key, Value: JSON-encoded value - """ - - def __init__( - self, - *, - preferences: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - updated_at: builtins.float = ..., - etag: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "etag", b"etag", "preferences", b"preferences", "updated_at", b"updated_at" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetPreferencesResponse: typing_extensions.TypeAlias = GetPreferencesResponse - -@typing.final -class SetPreferencesRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class PreferencesEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - PREFERENCES_FIELD_NUMBER: builtins.int - IF_MATCH_FIELD_NUMBER: builtins.int - CLIENT_UPDATED_AT_FIELD_NUMBER: builtins.int - MERGE_FIELD_NUMBER: builtins.int - if_match: builtins.str - """Optional ETag for conflict detection (if-match)""" - client_updated_at: builtins.float - """Client-side last update timestamp for conflict resolution""" - merge: builtins.bool - """Merge mode: if true, only updates provided keys; if false, replaces all""" - @property - def preferences( - self, - ) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Preferences to update as JSON strings - Key: preference key, Value: JSON-encoded value - """ - - def __init__( - self, - *, - preferences: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - if_match: builtins.str = ..., - client_updated_at: builtins.float = ..., - merge: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "client_updated_at", - b"client_updated_at", - "if_match", - b"if_match", - "merge", - b"merge", - "preferences", - b"preferences", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetPreferencesRequest: typing_extensions.TypeAlias = SetPreferencesRequest - -@typing.final -class SetPreferencesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class ServerPreferencesEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - SUCCESS_FIELD_NUMBER: builtins.int - CONFLICT_FIELD_NUMBER: builtins.int - SERVER_PREFERENCES_FIELD_NUMBER: builtins.int - SERVER_UPDATED_AT_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - CONFLICT_MESSAGE_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether the update succeeded""" - conflict: builtins.bool - """Whether a conflict was detected (client data was stale)""" - server_updated_at: builtins.float - """Server-side timestamp after update""" - etag: builtins.str - """New ETag after update""" - conflict_message: builtins.str - """Conflict details if conflict = true""" - @property - def server_preferences( - self, - ) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Server preferences after update (or current state if conflict)""" - - def __init__( - self, - *, - success: builtins.bool = ..., - conflict: builtins.bool = ..., - server_preferences: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - server_updated_at: builtins.float = ..., - etag: builtins.str = ..., - conflict_message: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "conflict", - b"conflict", - "conflict_message", - b"conflict_message", - "etag", - b"etag", - "server_preferences", - b"server_preferences", - "server_updated_at", - b"server_updated_at", - "success", - b"success", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetPreferencesResponse: typing_extensions.TypeAlias = SetPreferencesResponse - -@typing.final -class StartIntegrationSyncRequest(google.protobuf.message.Message): - """============================================================================= - Integration Sync Orchestration Messages (Sprint 9) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INTEGRATION_ID_FIELD_NUMBER: builtins.int - integration_id: builtins.str - """Integration ID to sync""" - def __init__( - self, - *, - integration_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_id", b"integration_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___StartIntegrationSyncRequest: typing_extensions.TypeAlias = StartIntegrationSyncRequest - -@typing.final -class StartIntegrationSyncResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SYNC_RUN_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - sync_run_id: builtins.str - """Unique sync run identifier""" - status: builtins.str - """Initial status (always "running")""" - def __init__( - self, - *, - sync_run_id: builtins.str = ..., - status: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "status", b"status", "sync_run_id", b"sync_run_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___StartIntegrationSyncResponse: typing_extensions.TypeAlias = StartIntegrationSyncResponse - -@typing.final -class GetSyncStatusRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SYNC_RUN_ID_FIELD_NUMBER: builtins.int - sync_run_id: builtins.str - """Sync run ID to check""" - def __init__( - self, - *, - sync_run_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["sync_run_id", b"sync_run_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetSyncStatusRequest: typing_extensions.TypeAlias = GetSyncStatusRequest - -@typing.final -class GetSyncStatusResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATUS_FIELD_NUMBER: builtins.int - ITEMS_SYNCED_FIELD_NUMBER: builtins.int - ITEMS_TOTAL_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - DURATION_MS_FIELD_NUMBER: builtins.int - ERROR_CODE_FIELD_NUMBER: builtins.int - EXPIRES_AT_FIELD_NUMBER: builtins.int - NOT_FOUND_REASON_FIELD_NUMBER: builtins.int - status: builtins.str - """Current status: "running", "success", "error" """ - items_synced: builtins.int - """Number of items synced (so far or total)""" - items_total: builtins.int - """Total items to sync (if known)""" - error_message: builtins.str - """Error message if status is "error" """ - duration_ms: builtins.int - """Duration in milliseconds (when completed)""" - error_code: Global___SyncErrorCode.ValueType - """Structured error code for programmatic error handling - (enables client to distinguish auth failures from other errors) - """ - expires_at: builtins.str - """When this sync run expires from cache (ISO 8601 timestamp) - (Sprint GAP-002: State Synchronization) - """ - not_found_reason: builtins.str - """Reason for NOT_FOUND: "expired" or "never_existed" - (Sprint GAP-002: State Synchronization) - """ - def __init__( - self, - *, - status: builtins.str = ..., - items_synced: builtins.int = ..., - items_total: builtins.int = ..., - error_message: builtins.str = ..., - duration_ms: builtins.int = ..., - error_code: Global___SyncErrorCode.ValueType = ..., - expires_at: builtins.str | None = ..., - not_found_reason: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_expires_at", - b"_expires_at", - "_not_found_reason", - b"_not_found_reason", - "expires_at", - b"expires_at", - "not_found_reason", - b"not_found_reason", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_expires_at", - b"_expires_at", - "_not_found_reason", - b"_not_found_reason", - "duration_ms", - b"duration_ms", - "error_code", - b"error_code", - "error_message", - b"error_message", - "expires_at", - b"expires_at", - "items_synced", - b"items_synced", - "items_total", - b"items_total", - "not_found_reason", - b"not_found_reason", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__expires_at: typing_extensions.TypeAlias = typing.Literal["expires_at"] - _WhichOneofArgType__expires_at: typing_extensions.TypeAlias = typing.Literal[ - "_expires_at", b"_expires_at" - ] - _WhichOneofReturnType__not_found_reason: typing_extensions.TypeAlias = typing.Literal[ - "not_found_reason" - ] - _WhichOneofArgType__not_found_reason: typing_extensions.TypeAlias = typing.Literal[ - "_not_found_reason", b"_not_found_reason" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__expires_at - ) -> _WhichOneofReturnType__expires_at | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__not_found_reason - ) -> _WhichOneofReturnType__not_found_reason | None: ... - -Global___GetSyncStatusResponse: typing_extensions.TypeAlias = GetSyncStatusResponse - -@typing.final -class ListSyncHistoryRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INTEGRATION_ID_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - integration_id: builtins.str - """Integration ID to list history for""" - limit: builtins.int - """Maximum runs to return (default: 20, max: 100)""" - offset: builtins.int - """Pagination offset""" - def __init__( - self, - *, - integration_id: builtins.str = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integration_id", b"integration_id", "limit", b"limit", "offset", b"offset" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSyncHistoryRequest: typing_extensions.TypeAlias = ListSyncHistoryRequest - -@typing.final -class ListSyncHistoryResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RUNS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total count of sync runs""" - @property - def runs( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___SyncRunProto]: - """Sync runs (newest first)""" - - def __init__( - self, - *, - runs: collections.abc.Iterable[Global___SyncRunProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "runs", b"runs", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSyncHistoryResponse: typing_extensions.TypeAlias = ListSyncHistoryResponse - -@typing.final -class SyncRunProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - INTEGRATION_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - ITEMS_SYNCED_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - DURATION_MS_FIELD_NUMBER: builtins.int - STARTED_AT_FIELD_NUMBER: builtins.int - COMPLETED_AT_FIELD_NUMBER: builtins.int - ERROR_CODE_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique sync run identifier""" - integration_id: builtins.str - """Integration ID""" - status: builtins.str - """Status: "running", "success", "error" """ - items_synced: builtins.int - """Number of items synced""" - error_message: builtins.str - """Error message if failed""" - duration_ms: builtins.int - """Duration in milliseconds""" - started_at: builtins.str - """Start timestamp (ISO 8601)""" - completed_at: builtins.str - """Completion timestamp (ISO 8601, empty if running)""" - error_code: Global___SyncErrorCode.ValueType - """Structured error code for programmatic error handling""" - def __init__( - self, - *, - id: builtins.str = ..., - integration_id: builtins.str = ..., - status: builtins.str = ..., - items_synced: builtins.int = ..., - error_message: builtins.str = ..., - duration_ms: builtins.int = ..., - started_at: builtins.str = ..., - completed_at: builtins.str = ..., - error_code: Global___SyncErrorCode.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "completed_at", - b"completed_at", - "duration_ms", - b"duration_ms", - "error_code", - b"error_code", - "error_message", - b"error_message", - "id", - b"id", - "integration_id", - b"integration_id", - "items_synced", - b"items_synced", - "started_at", - b"started_at", - "status", - b"status", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SyncRunProto: typing_extensions.TypeAlias = SyncRunProto - -@typing.final -class GetUserIntegrationsRequest(google.protobuf.message.Message): - """============================================================================= - Integration Cache Validation Messages (Sprint 18.1) - ============================================================================= - - Empty - uses identity context for user/workspace filtering - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetUserIntegrationsRequest: typing_extensions.TypeAlias = GetUserIntegrationsRequest - -@typing.final -class IntegrationInfo(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique integration identifier""" - name: builtins.str - """Display name (e.g., "Google Calendar")""" - type: builtins.str - """Integration type: "calendar", "pkm", "custom" """ - status: builtins.str - """Connection status: "connected", "disconnected", "error", "pending" """ - workspace_id: builtins.str - """Workspace ID that owns this integration""" - def __init__( - self, - *, - id: builtins.str = ..., - name: builtins.str = ..., - type: builtins.str = ..., - status: builtins.str = ..., - workspace_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "id", - b"id", - "name", - b"name", - "status", - b"status", - "type", - b"type", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___IntegrationInfo: typing_extensions.TypeAlias = IntegrationInfo - -@typing.final -class GetUserIntegrationsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INTEGRATIONS_FIELD_NUMBER: builtins.int - @property - def integrations( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___IntegrationInfo - ]: - """List of integrations for the current user/workspace""" - - def __init__( - self, - *, - integrations: collections.abc.Iterable[Global___IntegrationInfo] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "integrations", b"integrations" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetUserIntegrationsResponse: typing_extensions.TypeAlias = GetUserIntegrationsResponse - -@typing.final -class GetRecentLogsRequest(google.protobuf.message.Message): - """============================================================================= - Observability Messages (Sprint 9) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - LIMIT_FIELD_NUMBER: builtins.int - LEVEL_FIELD_NUMBER: builtins.int - SOURCE_FIELD_NUMBER: builtins.int - limit: builtins.int - """Maximum logs to return (default: 100, max: 1000)""" - level: builtins.str - """Filter by log level: debug, info, warning, error""" - source: builtins.str - """Filter by source: app, api, sync, auth, system""" - def __init__( - self, - *, - limit: builtins.int = ..., - level: builtins.str = ..., - source: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "level", b"level", "limit", b"limit", "source", b"source" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetRecentLogsRequest: typing_extensions.TypeAlias = GetRecentLogsRequest - -@typing.final -class GetRecentLogsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - LOGS_FIELD_NUMBER: builtins.int - @property - def logs( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___LogEntryProto - ]: - """Recent log entries""" - - def __init__( - self, - *, - logs: collections.abc.Iterable[Global___LogEntryProto] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["logs", b"logs"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetRecentLogsResponse: typing_extensions.TypeAlias = GetRecentLogsResponse - -@typing.final -class LogEntryProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class DetailsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - TIMESTAMP_FIELD_NUMBER: builtins.int - LEVEL_FIELD_NUMBER: builtins.int - SOURCE_FIELD_NUMBER: builtins.int - MESSAGE_FIELD_NUMBER: builtins.int - DETAILS_FIELD_NUMBER: builtins.int - TRACE_ID_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - EVENT_TYPE_FIELD_NUMBER: builtins.int - OPERATION_ID_FIELD_NUMBER: builtins.int - ENTITY_ID_FIELD_NUMBER: builtins.int - timestamp: builtins.str - """Timestamp (ISO 8601)""" - level: builtins.str - """Log level: debug, info, warning, error""" - source: builtins.str - """Source component: app, api, sync, auth, system""" - message: builtins.str - """Log message""" - trace_id: builtins.str - """Distributed tracing correlation ID""" - span_id: builtins.str - """Span ID within trace""" - event_type: builtins.str - """Semantic event type (e.g., "meeting.created", "summary.generated")""" - operation_id: builtins.str - """Groups related events (e.g., all events for one meeting session)""" - entity_id: builtins.str - """Primary entity ID (e.g., meeting_id for meeting events)""" - @property - def details(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Additional details (key-value pairs)""" - - def __init__( - self, - *, - timestamp: builtins.str = ..., - level: builtins.str = ..., - source: builtins.str = ..., - message: builtins.str = ..., - details: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - trace_id: builtins.str = ..., - span_id: builtins.str = ..., - event_type: builtins.str = ..., - operation_id: builtins.str = ..., - entity_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "details", - b"details", - "entity_id", - b"entity_id", - "event_type", - b"event_type", - "level", - b"level", - "message", - b"message", - "operation_id", - b"operation_id", - "source", - b"source", - "span_id", - b"span_id", - "timestamp", - b"timestamp", - "trace_id", - b"trace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___LogEntryProto: typing_extensions.TypeAlias = LogEntryProto - -@typing.final -class GetPerformanceMetricsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - HISTORY_LIMIT_FIELD_NUMBER: builtins.int - history_limit: builtins.int - """Number of historical data points (default: 60)""" - def __init__( - self, - *, - history_limit: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "history_limit", b"history_limit" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetPerformanceMetricsRequest: typing_extensions.TypeAlias = GetPerformanceMetricsRequest - -@typing.final -class GetPerformanceMetricsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CURRENT_FIELD_NUMBER: builtins.int - HISTORY_FIELD_NUMBER: builtins.int - @property - def current(self) -> Global___PerformanceMetricsPoint: - """Current metrics""" - - @property - def history( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___PerformanceMetricsPoint - ]: - """Historical metrics (oldest to newest)""" - - def __init__( - self, - *, - current: Global___PerformanceMetricsPoint | None = ..., - history: collections.abc.Iterable[Global___PerformanceMetricsPoint] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["current", b"current"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "current", b"current", "history", b"history" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetPerformanceMetricsResponse: typing_extensions.TypeAlias = GetPerformanceMetricsResponse - -@typing.final -class PerformanceMetricsPoint(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TIMESTAMP_FIELD_NUMBER: builtins.int - CPU_PERCENT_FIELD_NUMBER: builtins.int - MEMORY_PERCENT_FIELD_NUMBER: builtins.int - MEMORY_MB_FIELD_NUMBER: builtins.int - DISK_PERCENT_FIELD_NUMBER: builtins.int - NETWORK_BYTES_SENT_FIELD_NUMBER: builtins.int - NETWORK_BYTES_RECV_FIELD_NUMBER: builtins.int - PROCESS_MEMORY_MB_FIELD_NUMBER: builtins.int - ACTIVE_CONNECTIONS_FIELD_NUMBER: builtins.int - timestamp: builtins.float - """Unix timestamp""" - cpu_percent: builtins.float - """CPU usage percentage (0-100)""" - memory_percent: builtins.float - """Memory usage percentage (0-100)""" - memory_mb: builtins.float - """Memory used in megabytes""" - disk_percent: builtins.float - """Disk usage percentage (0-100)""" - network_bytes_sent: builtins.int - """Network bytes sent since last measurement""" - network_bytes_recv: builtins.int - """Network bytes received since last measurement""" - process_memory_mb: builtins.float - """NoteFlow process memory in megabytes""" - active_connections: builtins.int - """Active network connections""" - def __init__( - self, - *, - timestamp: builtins.float = ..., - cpu_percent: builtins.float = ..., - memory_percent: builtins.float = ..., - memory_mb: builtins.float = ..., - disk_percent: builtins.float = ..., - network_bytes_sent: builtins.int = ..., - network_bytes_recv: builtins.int = ..., - process_memory_mb: builtins.float = ..., - active_connections: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "active_connections", - b"active_connections", - "cpu_percent", - b"cpu_percent", - "disk_percent", - b"disk_percent", - "memory_mb", - b"memory_mb", - "memory_percent", - b"memory_percent", - "network_bytes_recv", - b"network_bytes_recv", - "network_bytes_sent", - b"network_bytes_sent", - "process_memory_mb", - b"process_memory_mb", - "timestamp", - b"timestamp", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___PerformanceMetricsPoint: typing_extensions.TypeAlias = PerformanceMetricsPoint - -@typing.final -class ClaimMappingProto(google.protobuf.message.Message): - """============================================================================= - OIDC Provider Management Messages (Sprint 17) - ============================================================================= - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUBJECT_CLAIM_FIELD_NUMBER: builtins.int - EMAIL_CLAIM_FIELD_NUMBER: builtins.int - EMAIL_VERIFIED_CLAIM_FIELD_NUMBER: builtins.int - NAME_CLAIM_FIELD_NUMBER: builtins.int - PREFERRED_USERNAME_CLAIM_FIELD_NUMBER: builtins.int - GROUPS_CLAIM_FIELD_NUMBER: builtins.int - PICTURE_CLAIM_FIELD_NUMBER: builtins.int - FIRST_NAME_CLAIM_FIELD_NUMBER: builtins.int - LAST_NAME_CLAIM_FIELD_NUMBER: builtins.int - PHONE_CLAIM_FIELD_NUMBER: builtins.int - subject_claim: builtins.str - """OIDC claim names mapped to user attributes""" - email_claim: builtins.str - email_verified_claim: builtins.str - name_claim: builtins.str - preferred_username_claim: builtins.str - groups_claim: builtins.str - picture_claim: builtins.str - first_name_claim: builtins.str - last_name_claim: builtins.str - phone_claim: builtins.str - def __init__( - self, - *, - subject_claim: builtins.str = ..., - email_claim: builtins.str = ..., - email_verified_claim: builtins.str = ..., - name_claim: builtins.str = ..., - preferred_username_claim: builtins.str = ..., - groups_claim: builtins.str = ..., - picture_claim: builtins.str = ..., - first_name_claim: builtins.str | None = ..., - last_name_claim: builtins.str | None = ..., - phone_claim: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_first_name_claim", - b"_first_name_claim", - "_last_name_claim", - b"_last_name_claim", - "_phone_claim", - b"_phone_claim", - "first_name_claim", - b"first_name_claim", - "last_name_claim", - b"last_name_claim", - "phone_claim", - b"phone_claim", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_first_name_claim", - b"_first_name_claim", - "_last_name_claim", - b"_last_name_claim", - "_phone_claim", - b"_phone_claim", - "email_claim", - b"email_claim", - "email_verified_claim", - b"email_verified_claim", - "first_name_claim", - b"first_name_claim", - "groups_claim", - b"groups_claim", - "last_name_claim", - b"last_name_claim", - "name_claim", - b"name_claim", - "phone_claim", - b"phone_claim", - "picture_claim", - b"picture_claim", - "preferred_username_claim", - b"preferred_username_claim", - "subject_claim", - b"subject_claim", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__first_name_claim: typing_extensions.TypeAlias = typing.Literal[ - "first_name_claim" - ] - _WhichOneofArgType__first_name_claim: typing_extensions.TypeAlias = typing.Literal[ - "_first_name_claim", b"_first_name_claim" - ] - _WhichOneofReturnType__last_name_claim: typing_extensions.TypeAlias = typing.Literal[ - "last_name_claim" - ] - _WhichOneofArgType__last_name_claim: typing_extensions.TypeAlias = typing.Literal[ - "_last_name_claim", b"_last_name_claim" - ] - _WhichOneofReturnType__phone_claim: typing_extensions.TypeAlias = typing.Literal["phone_claim"] - _WhichOneofArgType__phone_claim: typing_extensions.TypeAlias = typing.Literal[ - "_phone_claim", b"_phone_claim" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__first_name_claim - ) -> _WhichOneofReturnType__first_name_claim | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__last_name_claim - ) -> _WhichOneofReturnType__last_name_claim | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__phone_claim - ) -> _WhichOneofReturnType__phone_claim | None: ... - -Global___ClaimMappingProto: typing_extensions.TypeAlias = ClaimMappingProto - -@typing.final -class OidcDiscoveryProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ISSUER_FIELD_NUMBER: builtins.int - AUTHORIZATION_ENDPOINT_FIELD_NUMBER: builtins.int - TOKEN_ENDPOINT_FIELD_NUMBER: builtins.int - USERINFO_ENDPOINT_FIELD_NUMBER: builtins.int - JWKS_URI_FIELD_NUMBER: builtins.int - END_SESSION_ENDPOINT_FIELD_NUMBER: builtins.int - REVOCATION_ENDPOINT_FIELD_NUMBER: builtins.int - SCOPES_SUPPORTED_FIELD_NUMBER: builtins.int - CLAIMS_SUPPORTED_FIELD_NUMBER: builtins.int - SUPPORTS_PKCE_FIELD_NUMBER: builtins.int - issuer: builtins.str - """Discovery endpoint information""" - authorization_endpoint: builtins.str - token_endpoint: builtins.str - userinfo_endpoint: builtins.str - jwks_uri: builtins.str - end_session_endpoint: builtins.str - revocation_endpoint: builtins.str - supports_pkce: builtins.bool - @property - def scopes_supported( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - @property - def claims_supported( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - def __init__( - self, - *, - issuer: builtins.str = ..., - authorization_endpoint: builtins.str = ..., - token_endpoint: builtins.str = ..., - userinfo_endpoint: builtins.str | None = ..., - jwks_uri: builtins.str | None = ..., - end_session_endpoint: builtins.str | None = ..., - revocation_endpoint: builtins.str | None = ..., - scopes_supported: collections.abc.Iterable[builtins.str] | None = ..., - claims_supported: collections.abc.Iterable[builtins.str] | None = ..., - supports_pkce: builtins.bool = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_end_session_endpoint", - b"_end_session_endpoint", - "_jwks_uri", - b"_jwks_uri", - "_revocation_endpoint", - b"_revocation_endpoint", - "_userinfo_endpoint", - b"_userinfo_endpoint", - "end_session_endpoint", - b"end_session_endpoint", - "jwks_uri", - b"jwks_uri", - "revocation_endpoint", - b"revocation_endpoint", - "userinfo_endpoint", - b"userinfo_endpoint", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_end_session_endpoint", - b"_end_session_endpoint", - "_jwks_uri", - b"_jwks_uri", - "_revocation_endpoint", - b"_revocation_endpoint", - "_userinfo_endpoint", - b"_userinfo_endpoint", - "authorization_endpoint", - b"authorization_endpoint", - "claims_supported", - b"claims_supported", - "end_session_endpoint", - b"end_session_endpoint", - "issuer", - b"issuer", - "jwks_uri", - b"jwks_uri", - "revocation_endpoint", - b"revocation_endpoint", - "scopes_supported", - b"scopes_supported", - "supports_pkce", - b"supports_pkce", - "token_endpoint", - b"token_endpoint", - "userinfo_endpoint", - b"userinfo_endpoint", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__end_session_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "end_session_endpoint" - ] - _WhichOneofArgType__end_session_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "_end_session_endpoint", b"_end_session_endpoint" - ] - _WhichOneofReturnType__jwks_uri: typing_extensions.TypeAlias = typing.Literal["jwks_uri"] - _WhichOneofArgType__jwks_uri: typing_extensions.TypeAlias = typing.Literal[ - "_jwks_uri", b"_jwks_uri" - ] - _WhichOneofReturnType__revocation_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "revocation_endpoint" - ] - _WhichOneofArgType__revocation_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "_revocation_endpoint", b"_revocation_endpoint" - ] - _WhichOneofReturnType__userinfo_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "userinfo_endpoint" - ] - _WhichOneofArgType__userinfo_endpoint: typing_extensions.TypeAlias = typing.Literal[ - "_userinfo_endpoint", b"_userinfo_endpoint" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__end_session_endpoint - ) -> _WhichOneofReturnType__end_session_endpoint | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__jwks_uri - ) -> _WhichOneofReturnType__jwks_uri | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__revocation_endpoint - ) -> _WhichOneofReturnType__revocation_endpoint | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__userinfo_endpoint - ) -> _WhichOneofReturnType__userinfo_endpoint | None: ... - -Global___OidcDiscoveryProto: typing_extensions.TypeAlias = OidcDiscoveryProto - -@typing.final -class OidcProviderProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - PRESET_FIELD_NUMBER: builtins.int - ISSUER_URL_FIELD_NUMBER: builtins.int - CLIENT_ID_FIELD_NUMBER: builtins.int - ENABLED_FIELD_NUMBER: builtins.int - DISCOVERY_FIELD_NUMBER: builtins.int - CLAIM_MAPPING_FIELD_NUMBER: builtins.int - SCOPES_FIELD_NUMBER: builtins.int - REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: builtins.int - ALLOWED_GROUPS_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - DISCOVERY_REFRESHED_AT_FIELD_NUMBER: builtins.int - WARNINGS_FIELD_NUMBER: builtins.int - id: builtins.str - """Provider configuration""" - workspace_id: builtins.str - name: builtins.str - preset: builtins.str - issuer_url: builtins.str - client_id: builtins.str - enabled: builtins.bool - require_email_verified: builtins.bool - """Access control""" - created_at: builtins.int - """Timestamps""" - updated_at: builtins.int - discovery_refreshed_at: builtins.int - @property - def discovery(self) -> Global___OidcDiscoveryProto: - """Discovery configuration (populated from .well-known)""" - - @property - def claim_mapping(self) -> Global___ClaimMappingProto: - """Claim mapping configuration""" - - @property - def scopes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """OAuth scopes to request""" - - @property - def allowed_groups( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - @property - def warnings( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Validation warnings (only in responses)""" - - def __init__( - self, - *, - id: builtins.str = ..., - workspace_id: builtins.str = ..., - name: builtins.str = ..., - preset: builtins.str = ..., - issuer_url: builtins.str = ..., - client_id: builtins.str = ..., - enabled: builtins.bool = ..., - discovery: Global___OidcDiscoveryProto | None = ..., - claim_mapping: Global___ClaimMappingProto | None = ..., - scopes: collections.abc.Iterable[builtins.str] | None = ..., - require_email_verified: builtins.bool = ..., - allowed_groups: collections.abc.Iterable[builtins.str] | None = ..., - created_at: builtins.int = ..., - updated_at: builtins.int = ..., - discovery_refreshed_at: builtins.int | None = ..., - warnings: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_discovery", - b"_discovery", - "_discovery_refreshed_at", - b"_discovery_refreshed_at", - "claim_mapping", - b"claim_mapping", - "discovery", - b"discovery", - "discovery_refreshed_at", - b"discovery_refreshed_at", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_discovery", - b"_discovery", - "_discovery_refreshed_at", - b"_discovery_refreshed_at", - "allowed_groups", - b"allowed_groups", - "claim_mapping", - b"claim_mapping", - "client_id", - b"client_id", - "created_at", - b"created_at", - "discovery", - b"discovery", - "discovery_refreshed_at", - b"discovery_refreshed_at", - "enabled", - b"enabled", - "id", - b"id", - "issuer_url", - b"issuer_url", - "name", - b"name", - "preset", - b"preset", - "require_email_verified", - b"require_email_verified", - "scopes", - b"scopes", - "updated_at", - b"updated_at", - "warnings", - b"warnings", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__discovery: typing_extensions.TypeAlias = typing.Literal["discovery"] - _WhichOneofArgType__discovery: typing_extensions.TypeAlias = typing.Literal[ - "_discovery", b"_discovery" - ] - _WhichOneofReturnType__discovery_refreshed_at: typing_extensions.TypeAlias = typing.Literal[ - "discovery_refreshed_at" - ] - _WhichOneofArgType__discovery_refreshed_at: typing_extensions.TypeAlias = typing.Literal[ - "_discovery_refreshed_at", b"_discovery_refreshed_at" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__discovery - ) -> _WhichOneofReturnType__discovery | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__discovery_refreshed_at - ) -> _WhichOneofReturnType__discovery_refreshed_at | None: ... - -Global___OidcProviderProto: typing_extensions.TypeAlias = OidcProviderProto - -@typing.final -class RegisterOidcProviderRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - ISSUER_URL_FIELD_NUMBER: builtins.int - CLIENT_ID_FIELD_NUMBER: builtins.int - CLIENT_SECRET_FIELD_NUMBER: builtins.int - PRESET_FIELD_NUMBER: builtins.int - SCOPES_FIELD_NUMBER: builtins.int - CLAIM_MAPPING_FIELD_NUMBER: builtins.int - ALLOWED_GROUPS_FIELD_NUMBER: builtins.int - REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: builtins.int - AUTO_DISCOVER_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace to register provider in""" - name: builtins.str - """Display name for the provider""" - issuer_url: builtins.str - """OIDC issuer URL (base URL for discovery)""" - client_id: builtins.str - """OAuth client ID""" - client_secret: builtins.str - """Optional client secret (for confidential clients)""" - preset: builtins.str - """Provider preset: authentik, authelia, keycloak, auth0, okta, azure_ad, custom""" - require_email_verified: builtins.bool - """Whether to require verified email (default: true)""" - auto_discover: builtins.bool - """Whether to auto-discover endpoints (default: true)""" - @property - def scopes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional custom scopes (defaults to preset)""" - - @property - def claim_mapping(self) -> Global___ClaimMappingProto: - """Optional custom claim mapping (defaults to preset)""" - - @property - def allowed_groups( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional group-based access control""" - - def __init__( - self, - *, - workspace_id: builtins.str = ..., - name: builtins.str = ..., - issuer_url: builtins.str = ..., - client_id: builtins.str = ..., - client_secret: builtins.str | None = ..., - preset: builtins.str = ..., - scopes: collections.abc.Iterable[builtins.str] | None = ..., - claim_mapping: Global___ClaimMappingProto | None = ..., - allowed_groups: collections.abc.Iterable[builtins.str] | None = ..., - require_email_verified: builtins.bool | None = ..., - auto_discover: builtins.bool = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", - b"_claim_mapping", - "_client_secret", - b"_client_secret", - "_require_email_verified", - b"_require_email_verified", - "claim_mapping", - b"claim_mapping", - "client_secret", - b"client_secret", - "require_email_verified", - b"require_email_verified", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", - b"_claim_mapping", - "_client_secret", - b"_client_secret", - "_require_email_verified", - b"_require_email_verified", - "allowed_groups", - b"allowed_groups", - "auto_discover", - b"auto_discover", - "claim_mapping", - b"claim_mapping", - "client_id", - b"client_id", - "client_secret", - b"client_secret", - "issuer_url", - b"issuer_url", - "name", - b"name", - "preset", - b"preset", - "require_email_verified", - b"require_email_verified", - "scopes", - b"scopes", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__claim_mapping: typing_extensions.TypeAlias = typing.Literal[ - "claim_mapping" - ] - _WhichOneofArgType__claim_mapping: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", b"_claim_mapping" - ] - _WhichOneofReturnType__client_secret: typing_extensions.TypeAlias = typing.Literal[ - "client_secret" - ] - _WhichOneofArgType__client_secret: typing_extensions.TypeAlias = typing.Literal[ - "_client_secret", b"_client_secret" - ] - _WhichOneofReturnType__require_email_verified: typing_extensions.TypeAlias = typing.Literal[ - "require_email_verified" - ] - _WhichOneofArgType__require_email_verified: typing_extensions.TypeAlias = typing.Literal[ - "_require_email_verified", b"_require_email_verified" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__claim_mapping - ) -> _WhichOneofReturnType__claim_mapping | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__client_secret - ) -> _WhichOneofReturnType__client_secret | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__require_email_verified - ) -> _WhichOneofReturnType__require_email_verified | None: ... - -Global___RegisterOidcProviderRequest: typing_extensions.TypeAlias = RegisterOidcProviderRequest - -@typing.final -class ListOidcProvidersRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - ENABLED_ONLY_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Optional workspace filter""" - enabled_only: builtins.bool - """Filter to only enabled providers""" - def __init__( - self, - *, - workspace_id: builtins.str | None = ..., - enabled_only: builtins.bool = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_workspace_id", b"_workspace_id", "workspace_id", b"workspace_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_workspace_id", - b"_workspace_id", - "enabled_only", - b"enabled_only", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id" - ] - _WhichOneofArgType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "_workspace_id", b"_workspace_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__workspace_id - ) -> _WhichOneofReturnType__workspace_id | None: ... - -Global___ListOidcProvidersRequest: typing_extensions.TypeAlias = ListOidcProvidersRequest - -@typing.final -class ListOidcProvidersResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDERS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total count""" - @property - def providers( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___OidcProviderProto - ]: - """Registered OIDC providers""" - - def __init__( - self, - *, - providers: collections.abc.Iterable[Global___OidcProviderProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "providers", b"providers", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListOidcProvidersResponse: typing_extensions.TypeAlias = ListOidcProvidersResponse - -@typing.final -class GetOidcProviderRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_ID_FIELD_NUMBER: builtins.int - provider_id: builtins.str - """Provider ID to retrieve""" - def __init__( - self, - *, - provider_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["provider_id", b"provider_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetOidcProviderRequest: typing_extensions.TypeAlias = GetOidcProviderRequest - -@typing.final -class UpdateOidcProviderRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SCOPES_FIELD_NUMBER: builtins.int - CLAIM_MAPPING_FIELD_NUMBER: builtins.int - ALLOWED_GROUPS_FIELD_NUMBER: builtins.int - REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: builtins.int - ENABLED_FIELD_NUMBER: builtins.int - provider_id: builtins.str - """Provider ID to update""" - name: builtins.str - """Updated name (optional)""" - require_email_verified: builtins.bool - """Updated require_email_verified (optional)""" - enabled: builtins.bool - """Updated enabled status (optional)""" - @property - def scopes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Updated scopes (replaces existing)""" - - @property - def claim_mapping(self) -> Global___ClaimMappingProto: - """Updated claim mapping (optional)""" - - @property - def allowed_groups( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Updated allowed groups (replaces existing)""" - - def __init__( - self, - *, - provider_id: builtins.str = ..., - name: builtins.str | None = ..., - scopes: collections.abc.Iterable[builtins.str] | None = ..., - claim_mapping: Global___ClaimMappingProto | None = ..., - allowed_groups: collections.abc.Iterable[builtins.str] | None = ..., - require_email_verified: builtins.bool | None = ..., - enabled: builtins.bool | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", - b"_claim_mapping", - "_enabled", - b"_enabled", - "_name", - b"_name", - "_require_email_verified", - b"_require_email_verified", - "claim_mapping", - b"claim_mapping", - "enabled", - b"enabled", - "name", - b"name", - "require_email_verified", - b"require_email_verified", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", - b"_claim_mapping", - "_enabled", - b"_enabled", - "_name", - b"_name", - "_require_email_verified", - b"_require_email_verified", - "allowed_groups", - b"allowed_groups", - "claim_mapping", - b"claim_mapping", - "enabled", - b"enabled", - "name", - b"name", - "provider_id", - b"provider_id", - "require_email_verified", - b"require_email_verified", - "scopes", - b"scopes", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__claim_mapping: typing_extensions.TypeAlias = typing.Literal[ - "claim_mapping" - ] - _WhichOneofArgType__claim_mapping: typing_extensions.TypeAlias = typing.Literal[ - "_claim_mapping", b"_claim_mapping" - ] - _WhichOneofReturnType__enabled: typing_extensions.TypeAlias = typing.Literal["enabled"] - _WhichOneofArgType__enabled: typing_extensions.TypeAlias = typing.Literal[ - "_enabled", b"_enabled" - ] - _WhichOneofReturnType__name: typing_extensions.TypeAlias = typing.Literal["name"] - _WhichOneofArgType__name: typing_extensions.TypeAlias = typing.Literal["_name", b"_name"] - _WhichOneofReturnType__require_email_verified: typing_extensions.TypeAlias = typing.Literal[ - "require_email_verified" - ] - _WhichOneofArgType__require_email_verified: typing_extensions.TypeAlias = typing.Literal[ - "_require_email_verified", b"_require_email_verified" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__claim_mapping - ) -> _WhichOneofReturnType__claim_mapping | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__enabled - ) -> _WhichOneofReturnType__enabled | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__name - ) -> _WhichOneofReturnType__name | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__require_email_verified - ) -> _WhichOneofReturnType__require_email_verified | None: ... - -Global___UpdateOidcProviderRequest: typing_extensions.TypeAlias = UpdateOidcProviderRequest - -@typing.final -class DeleteOidcProviderRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_ID_FIELD_NUMBER: builtins.int - provider_id: builtins.str - """Provider ID to delete""" - def __init__( - self, - *, - provider_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["provider_id", b"provider_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteOidcProviderRequest: typing_extensions.TypeAlias = DeleteOidcProviderRequest - -@typing.final -class DeleteOidcProviderResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether deletion succeeded""" - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteOidcProviderResponse: typing_extensions.TypeAlias = DeleteOidcProviderResponse - -@typing.final -class RefreshOidcDiscoveryRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROVIDER_ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - provider_id: builtins.str - """Optional provider ID (if not set, refreshes all)""" - workspace_id: builtins.str - """Optional workspace filter (for refresh all)""" - def __init__( - self, - *, - provider_id: builtins.str | None = ..., - workspace_id: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_provider_id", - b"_provider_id", - "_workspace_id", - b"_workspace_id", - "provider_id", - b"provider_id", - "workspace_id", - b"workspace_id", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_provider_id", - b"_provider_id", - "_workspace_id", - b"_workspace_id", - "provider_id", - b"provider_id", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__provider_id: typing_extensions.TypeAlias = typing.Literal["provider_id"] - _WhichOneofArgType__provider_id: typing_extensions.TypeAlias = typing.Literal[ - "_provider_id", b"_provider_id" - ] - _WhichOneofReturnType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id" - ] - _WhichOneofArgType__workspace_id: typing_extensions.TypeAlias = typing.Literal[ - "_workspace_id", b"_workspace_id" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__provider_id - ) -> _WhichOneofReturnType__provider_id | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__workspace_id - ) -> _WhichOneofReturnType__workspace_id | None: ... - -Global___RefreshOidcDiscoveryRequest: typing_extensions.TypeAlias = RefreshOidcDiscoveryRequest - -@typing.final -class RefreshOidcDiscoveryResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing.final - class ResultsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "key", b"key", "value", b"value" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - - RESULTS_FIELD_NUMBER: builtins.int - SUCCESS_COUNT_FIELD_NUMBER: builtins.int - FAILURE_COUNT_FIELD_NUMBER: builtins.int - success_count: builtins.int - """Count of successful refreshes""" - failure_count: builtins.int - """Count of failed refreshes""" - @property - def results(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Results per provider: provider_id -> error message (empty if success)""" - - def __init__( - self, - *, - results: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - success_count: builtins.int = ..., - failure_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "failure_count", b"failure_count", "results", b"results", "success_count", b"success_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RefreshOidcDiscoveryResponse: typing_extensions.TypeAlias = RefreshOidcDiscoveryResponse - -@typing.final -class ListOidcPresetsRequest(google.protobuf.message.Message): - """No parameters needed""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___ListOidcPresetsRequest: typing_extensions.TypeAlias = ListOidcPresetsRequest - -@typing.final -class OidcPresetProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PRESET_FIELD_NUMBER: builtins.int - DISPLAY_NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - DEFAULT_SCOPES_FIELD_NUMBER: builtins.int - DOCUMENTATION_URL_FIELD_NUMBER: builtins.int - NOTES_FIELD_NUMBER: builtins.int - preset: builtins.str - """Preset identifier""" - display_name: builtins.str - """Display name""" - description: builtins.str - """Description""" - documentation_url: builtins.str - """Documentation URL""" - notes: builtins.str - """Configuration notes""" - @property - def default_scopes( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Default scopes""" - - def __init__( - self, - *, - preset: builtins.str = ..., - display_name: builtins.str = ..., - description: builtins.str = ..., - default_scopes: collections.abc.Iterable[builtins.str] | None = ..., - documentation_url: builtins.str | None = ..., - notes: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_documentation_url", - b"_documentation_url", - "_notes", - b"_notes", - "documentation_url", - b"documentation_url", - "notes", - b"notes", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_documentation_url", - b"_documentation_url", - "_notes", - b"_notes", - "default_scopes", - b"default_scopes", - "description", - b"description", - "display_name", - b"display_name", - "documentation_url", - b"documentation_url", - "notes", - b"notes", - "preset", - b"preset", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__documentation_url: typing_extensions.TypeAlias = typing.Literal[ - "documentation_url" - ] - _WhichOneofArgType__documentation_url: typing_extensions.TypeAlias = typing.Literal[ - "_documentation_url", b"_documentation_url" - ] - _WhichOneofReturnType__notes: typing_extensions.TypeAlias = typing.Literal["notes"] - _WhichOneofArgType__notes: typing_extensions.TypeAlias = typing.Literal["_notes", b"_notes"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__documentation_url - ) -> _WhichOneofReturnType__documentation_url | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__notes - ) -> _WhichOneofReturnType__notes | None: ... - -Global___OidcPresetProto: typing_extensions.TypeAlias = OidcPresetProto - -@typing.final -class ListOidcPresetsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PRESETS_FIELD_NUMBER: builtins.int - @property - def presets( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___OidcPresetProto - ]: - """Available presets""" - - def __init__( - self, - *, - presets: collections.abc.Iterable[Global___OidcPresetProto] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["presets", b"presets"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListOidcPresetsResponse: typing_extensions.TypeAlias = ListOidcPresetsResponse - -@typing.final -class ExportRulesProto(google.protobuf.message.Message): - """Export configuration for a project""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DEFAULT_FORMAT_FIELD_NUMBER: builtins.int - INCLUDE_AUDIO_FIELD_NUMBER: builtins.int - INCLUDE_TIMESTAMPS_FIELD_NUMBER: builtins.int - TEMPLATE_ID_FIELD_NUMBER: builtins.int - default_format: Global___ExportFormat.ValueType - """Default export format (markdown, html, pdf)""" - include_audio: builtins.bool - """Whether to include audio file in exports""" - include_timestamps: builtins.bool - """Whether to include timestamps in transcript""" - template_id: builtins.str - """ID of export template to use""" - def __init__( - self, - *, - default_format: Global___ExportFormat.ValueType | None = ..., - include_audio: builtins.bool | None = ..., - include_timestamps: builtins.bool | None = ..., - template_id: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_format", - b"_default_format", - "_include_audio", - b"_include_audio", - "_include_timestamps", - b"_include_timestamps", - "_template_id", - b"_template_id", - "default_format", - b"default_format", - "include_audio", - b"include_audio", - "include_timestamps", - b"include_timestamps", - "template_id", - b"template_id", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_format", - b"_default_format", - "_include_audio", - b"_include_audio", - "_include_timestamps", - b"_include_timestamps", - "_template_id", - b"_template_id", - "default_format", - b"default_format", - "include_audio", - b"include_audio", - "include_timestamps", - b"include_timestamps", - "template_id", - b"template_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__default_format: typing_extensions.TypeAlias = typing.Literal[ - "default_format" - ] - _WhichOneofArgType__default_format: typing_extensions.TypeAlias = typing.Literal[ - "_default_format", b"_default_format" - ] - _WhichOneofReturnType__include_audio: typing_extensions.TypeAlias = typing.Literal[ - "include_audio" - ] - _WhichOneofArgType__include_audio: typing_extensions.TypeAlias = typing.Literal[ - "_include_audio", b"_include_audio" - ] - _WhichOneofReturnType__include_timestamps: typing_extensions.TypeAlias = typing.Literal[ - "include_timestamps" - ] - _WhichOneofArgType__include_timestamps: typing_extensions.TypeAlias = typing.Literal[ - "_include_timestamps", b"_include_timestamps" - ] - _WhichOneofReturnType__template_id: typing_extensions.TypeAlias = typing.Literal["template_id"] - _WhichOneofArgType__template_id: typing_extensions.TypeAlias = typing.Literal[ - "_template_id", b"_template_id" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__default_format - ) -> _WhichOneofReturnType__default_format | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__include_audio - ) -> _WhichOneofReturnType__include_audio | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__include_timestamps - ) -> _WhichOneofReturnType__include_timestamps | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__template_id - ) -> _WhichOneofReturnType__template_id | None: ... - -Global___ExportRulesProto: typing_extensions.TypeAlias = ExportRulesProto - -@typing.final -class TriggerRulesProto(google.protobuf.message.Message): - """Trigger configuration for a project""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - AUTO_START_ENABLED_FIELD_NUMBER: builtins.int - CALENDAR_MATCH_PATTERNS_FIELD_NUMBER: builtins.int - APP_MATCH_PATTERNS_FIELD_NUMBER: builtins.int - auto_start_enabled: builtins.bool - """Whether auto-start recording is enabled""" - @property - def calendar_match_patterns( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Glob patterns for calendar event titles""" - - @property - def app_match_patterns( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Glob patterns for application names""" - - def __init__( - self, - *, - auto_start_enabled: builtins.bool | None = ..., - calendar_match_patterns: collections.abc.Iterable[builtins.str] | None = ..., - app_match_patterns: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_auto_start_enabled", b"_auto_start_enabled", "auto_start_enabled", b"auto_start_enabled" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_auto_start_enabled", - b"_auto_start_enabled", - "app_match_patterns", - b"app_match_patterns", - "auto_start_enabled", - b"auto_start_enabled", - "calendar_match_patterns", - b"calendar_match_patterns", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__auto_start_enabled: typing_extensions.TypeAlias = typing.Literal[ - "auto_start_enabled" - ] - _WhichOneofArgType__auto_start_enabled: typing_extensions.TypeAlias = typing.Literal[ - "_auto_start_enabled", b"_auto_start_enabled" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__auto_start_enabled - ) -> _WhichOneofReturnType__auto_start_enabled | None: ... - -Global___TriggerRulesProto: typing_extensions.TypeAlias = TriggerRulesProto - -@typing.final -class WorkspaceSettingsProto(google.protobuf.message.Message): - """Workspace settings (inheritable defaults)""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - EXPORT_RULES_FIELD_NUMBER: builtins.int - TRIGGER_RULES_FIELD_NUMBER: builtins.int - RAG_ENABLED_FIELD_NUMBER: builtins.int - DEFAULT_SUMMARIZATION_TEMPLATE_FIELD_NUMBER: builtins.int - rag_enabled: builtins.bool - """Whether RAG Q&A is enabled for this workspace""" - default_summarization_template: builtins.str - """Default summarization template ID""" - @property - def export_rules(self) -> Global___ExportRulesProto: - """Export configuration""" - - @property - def trigger_rules(self) -> Global___TriggerRulesProto: - """Trigger configuration""" - - def __init__( - self, - *, - export_rules: Global___ExportRulesProto | None = ..., - trigger_rules: Global___TriggerRulesProto | None = ..., - rag_enabled: builtins.bool | None = ..., - default_summarization_template: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_summarization_template", - b"_default_summarization_template", - "_export_rules", - b"_export_rules", - "_rag_enabled", - b"_rag_enabled", - "_trigger_rules", - b"_trigger_rules", - "default_summarization_template", - b"default_summarization_template", - "export_rules", - b"export_rules", - "rag_enabled", - b"rag_enabled", - "trigger_rules", - b"trigger_rules", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_summarization_template", - b"_default_summarization_template", - "_export_rules", - b"_export_rules", - "_rag_enabled", - b"_rag_enabled", - "_trigger_rules", - b"_trigger_rules", - "default_summarization_template", - b"default_summarization_template", - "export_rules", - b"export_rules", - "rag_enabled", - b"rag_enabled", - "trigger_rules", - b"trigger_rules", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__default_summarization_template: typing_extensions.TypeAlias = ( - typing.Literal["default_summarization_template"] - ) - _WhichOneofArgType__default_summarization_template: typing_extensions.TypeAlias = ( - typing.Literal["_default_summarization_template", b"_default_summarization_template"] - ) - _WhichOneofReturnType__export_rules: typing_extensions.TypeAlias = typing.Literal[ - "export_rules" - ] - _WhichOneofArgType__export_rules: typing_extensions.TypeAlias = typing.Literal[ - "_export_rules", b"_export_rules" - ] - _WhichOneofReturnType__rag_enabled: typing_extensions.TypeAlias = typing.Literal["rag_enabled"] - _WhichOneofArgType__rag_enabled: typing_extensions.TypeAlias = typing.Literal[ - "_rag_enabled", b"_rag_enabled" - ] - _WhichOneofReturnType__trigger_rules: typing_extensions.TypeAlias = typing.Literal[ - "trigger_rules" - ] - _WhichOneofArgType__trigger_rules: typing_extensions.TypeAlias = typing.Literal[ - "_trigger_rules", b"_trigger_rules" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__default_summarization_template - ) -> _WhichOneofReturnType__default_summarization_template | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__export_rules - ) -> _WhichOneofReturnType__export_rules | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__rag_enabled - ) -> _WhichOneofReturnType__rag_enabled | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__trigger_rules - ) -> _WhichOneofReturnType__trigger_rules | None: ... - -Global___WorkspaceSettingsProto: typing_extensions.TypeAlias = WorkspaceSettingsProto - -@typing.final -class ProjectSettingsProto(google.protobuf.message.Message): - """Project settings (inheritable from workspace)""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - EXPORT_RULES_FIELD_NUMBER: builtins.int - TRIGGER_RULES_FIELD_NUMBER: builtins.int - RAG_ENABLED_FIELD_NUMBER: builtins.int - DEFAULT_SUMMARIZATION_TEMPLATE_FIELD_NUMBER: builtins.int - rag_enabled: builtins.bool - """Whether RAG Q&A is enabled for this project""" - default_summarization_template: builtins.str - """Default summarization template ID""" - @property - def export_rules(self) -> Global___ExportRulesProto: - """Export configuration""" - - @property - def trigger_rules(self) -> Global___TriggerRulesProto: - """Trigger configuration""" - - def __init__( - self, - *, - export_rules: Global___ExportRulesProto | None = ..., - trigger_rules: Global___TriggerRulesProto | None = ..., - rag_enabled: builtins.bool | None = ..., - default_summarization_template: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_summarization_template", - b"_default_summarization_template", - "_export_rules", - b"_export_rules", - "_rag_enabled", - b"_rag_enabled", - "_trigger_rules", - b"_trigger_rules", - "default_summarization_template", - b"default_summarization_template", - "export_rules", - b"export_rules", - "rag_enabled", - b"rag_enabled", - "trigger_rules", - b"trigger_rules", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_default_summarization_template", - b"_default_summarization_template", - "_export_rules", - b"_export_rules", - "_rag_enabled", - b"_rag_enabled", - "_trigger_rules", - b"_trigger_rules", - "default_summarization_template", - b"default_summarization_template", - "export_rules", - b"export_rules", - "rag_enabled", - b"rag_enabled", - "trigger_rules", - b"trigger_rules", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__default_summarization_template: typing_extensions.TypeAlias = ( - typing.Literal["default_summarization_template"] - ) - _WhichOneofArgType__default_summarization_template: typing_extensions.TypeAlias = ( - typing.Literal["_default_summarization_template", b"_default_summarization_template"] - ) - _WhichOneofReturnType__export_rules: typing_extensions.TypeAlias = typing.Literal[ - "export_rules" - ] - _WhichOneofArgType__export_rules: typing_extensions.TypeAlias = typing.Literal[ - "_export_rules", b"_export_rules" - ] - _WhichOneofReturnType__rag_enabled: typing_extensions.TypeAlias = typing.Literal["rag_enabled"] - _WhichOneofArgType__rag_enabled: typing_extensions.TypeAlias = typing.Literal[ - "_rag_enabled", b"_rag_enabled" - ] - _WhichOneofReturnType__trigger_rules: typing_extensions.TypeAlias = typing.Literal[ - "trigger_rules" - ] - _WhichOneofArgType__trigger_rules: typing_extensions.TypeAlias = typing.Literal[ - "_trigger_rules", b"_trigger_rules" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__default_summarization_template - ) -> _WhichOneofReturnType__default_summarization_template | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__export_rules - ) -> _WhichOneofReturnType__export_rules | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__rag_enabled - ) -> _WhichOneofReturnType__rag_enabled | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__trigger_rules - ) -> _WhichOneofReturnType__trigger_rules | None: ... - -Global___ProjectSettingsProto: typing_extensions.TypeAlias = ProjectSettingsProto - -@typing.final -class ProjectProto(google.protobuf.message.Message): - """Full project entity""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SLUG_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - IS_DEFAULT_FIELD_NUMBER: builtins.int - IS_ARCHIVED_FIELD_NUMBER: builtins.int - SETTINGS_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - ARCHIVED_AT_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique project identifier""" - workspace_id: builtins.str - """Parent workspace identifier""" - name: builtins.str - """User-provided project name""" - slug: builtins.str - """URL-friendly identifier (unique per workspace)""" - description: builtins.str - """Optional project description""" - is_default: builtins.bool - """Whether this is the workspace's default project""" - is_archived: builtins.bool - """Whether the project is archived""" - created_at: builtins.int - """Creation timestamp (Unix epoch seconds)""" - updated_at: builtins.int - """Last modification timestamp (Unix epoch seconds)""" - archived_at: builtins.int - """Archive timestamp (Unix epoch seconds, 0 if not archived)""" - @property - def settings(self) -> Global___ProjectSettingsProto: - """Project-level settings""" - - def __init__( - self, - *, - id: builtins.str = ..., - workspace_id: builtins.str = ..., - name: builtins.str = ..., - slug: builtins.str | None = ..., - description: builtins.str | None = ..., - is_default: builtins.bool = ..., - is_archived: builtins.bool = ..., - settings: Global___ProjectSettingsProto | None = ..., - created_at: builtins.int = ..., - updated_at: builtins.int = ..., - archived_at: builtins.int | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_archived_at", - b"_archived_at", - "_description", - b"_description", - "_settings", - b"_settings", - "_slug", - b"_slug", - "archived_at", - b"archived_at", - "description", - b"description", - "settings", - b"settings", - "slug", - b"slug", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_archived_at", - b"_archived_at", - "_description", - b"_description", - "_settings", - b"_settings", - "_slug", - b"_slug", - "archived_at", - b"archived_at", - "created_at", - b"created_at", - "description", - b"description", - "id", - b"id", - "is_archived", - b"is_archived", - "is_default", - b"is_default", - "name", - b"name", - "settings", - b"settings", - "slug", - b"slug", - "updated_at", - b"updated_at", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__archived_at: typing_extensions.TypeAlias = typing.Literal["archived_at"] - _WhichOneofArgType__archived_at: typing_extensions.TypeAlias = typing.Literal[ - "_archived_at", b"_archived_at" - ] - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - _WhichOneofReturnType__settings: typing_extensions.TypeAlias = typing.Literal["settings"] - _WhichOneofArgType__settings: typing_extensions.TypeAlias = typing.Literal[ - "_settings", b"_settings" - ] - _WhichOneofReturnType__slug: typing_extensions.TypeAlias = typing.Literal["slug"] - _WhichOneofArgType__slug: typing_extensions.TypeAlias = typing.Literal["_slug", b"_slug"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__archived_at - ) -> _WhichOneofReturnType__archived_at | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__settings - ) -> _WhichOneofReturnType__settings | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__slug - ) -> _WhichOneofReturnType__slug | None: ... - -Global___ProjectProto: typing_extensions.TypeAlias = ProjectProto - -@typing.final -class ProjectMembershipProto(google.protobuf.message.Message): - """Project membership entity""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - USER_ID_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - JOINED_AT_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project identifier""" - user_id: builtins.str - """User identifier""" - role: Global___ProjectRoleProto.ValueType - """User's role in the project""" - joined_at: builtins.int - """When the user joined the project (Unix epoch seconds)""" - def __init__( - self, - *, - project_id: builtins.str = ..., - user_id: builtins.str = ..., - role: Global___ProjectRoleProto.ValueType = ..., - joined_at: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "joined_at", - b"joined_at", - "project_id", - b"project_id", - "role", - b"role", - "user_id", - b"user_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ProjectMembershipProto: typing_extensions.TypeAlias = ProjectMembershipProto - -@typing.final -class CreateProjectRequest(google.protobuf.message.Message): - """----------------------------------------------------------------------------- - Project CRUD Request/Response Messages - ----------------------------------------------------------------------------- - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SLUG_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - SETTINGS_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace to create project in""" - name: builtins.str - """Project name""" - slug: builtins.str - """Optional URL-friendly slug (auto-generated from name if not provided)""" - description: builtins.str - """Optional project description""" - @property - def settings(self) -> Global___ProjectSettingsProto: - """Optional project settings""" - - def __init__( - self, - *, - workspace_id: builtins.str = ..., - name: builtins.str = ..., - slug: builtins.str | None = ..., - description: builtins.str | None = ..., - settings: Global___ProjectSettingsProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_description", - b"_description", - "_settings", - b"_settings", - "_slug", - b"_slug", - "description", - b"description", - "settings", - b"settings", - "slug", - b"slug", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_description", - b"_description", - "_settings", - b"_settings", - "_slug", - b"_slug", - "description", - b"description", - "name", - b"name", - "settings", - b"settings", - "slug", - b"slug", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - _WhichOneofReturnType__settings: typing_extensions.TypeAlias = typing.Literal["settings"] - _WhichOneofArgType__settings: typing_extensions.TypeAlias = typing.Literal[ - "_settings", b"_settings" - ] - _WhichOneofReturnType__slug: typing_extensions.TypeAlias = typing.Literal["slug"] - _WhichOneofArgType__slug: typing_extensions.TypeAlias = typing.Literal["_slug", b"_slug"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__settings - ) -> _WhichOneofReturnType__settings | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__slug - ) -> _WhichOneofReturnType__slug | None: ... - -Global___CreateProjectRequest: typing_extensions.TypeAlias = CreateProjectRequest - -@typing.final -class GetProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID to retrieve""" - def __init__( - self, - *, - project_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["project_id", b"project_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetProjectRequest: typing_extensions.TypeAlias = GetProjectRequest - -@typing.final -class GetProjectBySlugRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - SLUG_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace ID""" - slug: builtins.str - """Project slug""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - slug: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "slug", b"slug", "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetProjectBySlugRequest: typing_extensions.TypeAlias = GetProjectBySlugRequest - -@typing.final -class ListProjectsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - INCLUDE_ARCHIVED_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace to list projects for""" - include_archived: builtins.bool - """Whether to include archived projects (default: false)""" - limit: builtins.int - """Maximum projects to return (default: 50)""" - offset: builtins.int - """Pagination offset""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - include_archived: builtins.bool = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "include_archived", - b"include_archived", - "limit", - b"limit", - "offset", - b"offset", - "workspace_id", - b"workspace_id", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListProjectsRequest: typing_extensions.TypeAlias = ListProjectsRequest - -@typing.final -class ListProjectsResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECTS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total count (for pagination)""" - @property - def projects( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[Global___ProjectProto]: - """Projects in the workspace""" - - def __init__( - self, - *, - projects: collections.abc.Iterable[Global___ProjectProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "projects", b"projects", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListProjectsResponse: typing_extensions.TypeAlias = ListProjectsResponse - -@typing.final -class UpdateProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SLUG_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - SETTINGS_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID to update""" - name: builtins.str - """Updated name (optional)""" - slug: builtins.str - """Updated slug (optional)""" - description: builtins.str - """Updated description (optional)""" - @property - def settings(self) -> Global___ProjectSettingsProto: - """Updated settings (optional, replaces existing)""" - - def __init__( - self, - *, - project_id: builtins.str = ..., - name: builtins.str | None = ..., - slug: builtins.str | None = ..., - description: builtins.str | None = ..., - settings: Global___ProjectSettingsProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_description", - b"_description", - "_name", - b"_name", - "_settings", - b"_settings", - "_slug", - b"_slug", - "description", - b"description", - "name", - b"name", - "settings", - b"settings", - "slug", - b"slug", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_description", - b"_description", - "_name", - b"_name", - "_settings", - b"_settings", - "_slug", - b"_slug", - "description", - b"description", - "name", - b"name", - "project_id", - b"project_id", - "settings", - b"settings", - "slug", - b"slug", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__description: typing_extensions.TypeAlias = typing.Literal["description"] - _WhichOneofArgType__description: typing_extensions.TypeAlias = typing.Literal[ - "_description", b"_description" - ] - _WhichOneofReturnType__name: typing_extensions.TypeAlias = typing.Literal["name"] - _WhichOneofArgType__name: typing_extensions.TypeAlias = typing.Literal["_name", b"_name"] - _WhichOneofReturnType__settings: typing_extensions.TypeAlias = typing.Literal["settings"] - _WhichOneofArgType__settings: typing_extensions.TypeAlias = typing.Literal[ - "_settings", b"_settings" - ] - _WhichOneofReturnType__slug: typing_extensions.TypeAlias = typing.Literal["slug"] - _WhichOneofArgType__slug: typing_extensions.TypeAlias = typing.Literal["_slug", b"_slug"] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__description - ) -> _WhichOneofReturnType__description | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__name - ) -> _WhichOneofReturnType__name | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__settings - ) -> _WhichOneofReturnType__settings | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__slug - ) -> _WhichOneofReturnType__slug | None: ... - -Global___UpdateProjectRequest: typing_extensions.TypeAlias = UpdateProjectRequest - -@typing.final -class ArchiveProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID to archive""" - def __init__( - self, - *, - project_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["project_id", b"project_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ArchiveProjectRequest: typing_extensions.TypeAlias = ArchiveProjectRequest - -@typing.final -class RestoreProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID to restore""" - def __init__( - self, - *, - project_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["project_id", b"project_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RestoreProjectRequest: typing_extensions.TypeAlias = RestoreProjectRequest - -@typing.final -class DeleteProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID to delete""" - def __init__( - self, - *, - project_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["project_id", b"project_id"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteProjectRequest: typing_extensions.TypeAlias = DeleteProjectRequest - -@typing.final -class DeleteProjectResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether deletion succeeded""" - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DeleteProjectResponse: typing_extensions.TypeAlias = DeleteProjectResponse - -@typing.final -class SetActiveProjectRequest(google.protobuf.message.Message): - """----------------------------------------------------------------------------- - Active Project Request/Response Messages - ----------------------------------------------------------------------------- - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace scope""" - project_id: builtins.str - """Project ID to set as active (empty to clear)""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - project_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "project_id", b"project_id", "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SetActiveProjectRequest: typing_extensions.TypeAlias = SetActiveProjectRequest - -@typing.final -class SetActiveProjectResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___SetActiveProjectResponse: typing_extensions.TypeAlias = SetActiveProjectResponse - -@typing.final -class GetActiveProjectRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace scope""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetActiveProjectRequest: typing_extensions.TypeAlias = GetActiveProjectRequest - -@typing.final -class GetActiveProjectResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - PROJECT_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Active project ID (unset if workspace default is used)""" - @property - def project(self) -> Global___ProjectProto: - """Resolved project (default if none set)""" - - def __init__( - self, - *, - project_id: builtins.str | None = ..., - project: Global___ProjectProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project", b"project", "project_id", b"project_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project", b"project", "project_id", b"project_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___GetActiveProjectResponse: typing_extensions.TypeAlias = GetActiveProjectResponse - -@typing.final -class AddProjectMemberRequest(google.protobuf.message.Message): - """----------------------------------------------------------------------------- - Project Membership Request/Response Messages - ----------------------------------------------------------------------------- - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - USER_ID_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID""" - user_id: builtins.str - """User ID to add""" - role: Global___ProjectRoleProto.ValueType - """Role to assign""" - def __init__( - self, - *, - project_id: builtins.str = ..., - user_id: builtins.str = ..., - role: Global___ProjectRoleProto.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "project_id", b"project_id", "role", b"role", "user_id", b"user_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___AddProjectMemberRequest: typing_extensions.TypeAlias = AddProjectMemberRequest - -@typing.final -class UpdateProjectMemberRoleRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - USER_ID_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID""" - user_id: builtins.str - """User ID""" - role: Global___ProjectRoleProto.ValueType - """New role""" - def __init__( - self, - *, - project_id: builtins.str = ..., - user_id: builtins.str = ..., - role: Global___ProjectRoleProto.ValueType = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "project_id", b"project_id", "role", b"role", "user_id", b"user_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateProjectMemberRoleRequest: typing_extensions.TypeAlias = ( - UpdateProjectMemberRoleRequest -) - -@typing.final -class RemoveProjectMemberRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - USER_ID_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID""" - user_id: builtins.str - """User ID to remove""" - def __init__( - self, - *, - project_id: builtins.str = ..., - user_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "project_id", b"project_id", "user_id", b"user_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RemoveProjectMemberRequest: typing_extensions.TypeAlias = RemoveProjectMemberRequest - -@typing.final -class RemoveProjectMemberResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether removal succeeded""" - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___RemoveProjectMemberResponse: typing_extensions.TypeAlias = RemoveProjectMemberResponse - -@typing.final -class ListProjectMembersRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PROJECT_ID_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - project_id: builtins.str - """Project ID""" - limit: builtins.int - """Maximum members to return (default: 100)""" - offset: builtins.int - """Pagination offset""" - def __init__( - self, - *, - project_id: builtins.str = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "limit", b"limit", "offset", b"offset", "project_id", b"project_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListProjectMembersRequest: typing_extensions.TypeAlias = ListProjectMembersRequest - -@typing.final -class ListProjectMembersResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEMBERS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total count""" - @property - def members( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___ProjectMembershipProto - ]: - """Project members""" - - def __init__( - self, - *, - members: collections.abc.Iterable[Global___ProjectMembershipProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "members", b"members", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListProjectMembersResponse: typing_extensions.TypeAlias = ListProjectMembersResponse - -@typing.final -class GetCurrentUserRequest(google.protobuf.message.Message): - """============================================================================= - Identity Management Messages (Sprint 16+) - ============================================================================= - - Empty - user ID comes from request headers - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -Global___GetCurrentUserRequest: typing_extensions.TypeAlias = GetCurrentUserRequest - -@typing.final -class GetCurrentUserResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - USER_ID_FIELD_NUMBER: builtins.int - WORKSPACE_ID_FIELD_NUMBER: builtins.int - DISPLAY_NAME_FIELD_NUMBER: builtins.int - EMAIL_FIELD_NUMBER: builtins.int - IS_AUTHENTICATED_FIELD_NUMBER: builtins.int - AUTH_PROVIDER_FIELD_NUMBER: builtins.int - WORKSPACE_NAME_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - user_id: builtins.str - """User ID (UUID string)""" - workspace_id: builtins.str - """Current workspace ID (UUID string)""" - display_name: builtins.str - """User display name""" - email: builtins.str - """User email (optional)""" - is_authenticated: builtins.bool - """Whether user is authenticated (vs local mode)""" - auth_provider: builtins.str - """OAuth provider if authenticated (google, outlook, etc.)""" - workspace_name: builtins.str - """Workspace name""" - role: builtins.str - """User's role in workspace""" - def __init__( - self, - *, - user_id: builtins.str = ..., - workspace_id: builtins.str = ..., - display_name: builtins.str = ..., - email: builtins.str = ..., - is_authenticated: builtins.bool = ..., - auth_provider: builtins.str = ..., - workspace_name: builtins.str = ..., - role: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "auth_provider", - b"auth_provider", - "display_name", - b"display_name", - "email", - b"email", - "is_authenticated", - b"is_authenticated", - "role", - b"role", - "user_id", - b"user_id", - "workspace_id", - b"workspace_id", - "workspace_name", - b"workspace_name", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetCurrentUserResponse: typing_extensions.TypeAlias = GetCurrentUserResponse - -@typing.final -class WorkspaceProto(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - SLUG_FIELD_NUMBER: builtins.int - IS_DEFAULT_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - id: builtins.str - """Workspace ID (UUID string)""" - name: builtins.str - """Workspace name""" - slug: builtins.str - """URL slug""" - is_default: builtins.bool - """Whether this is the default workspace""" - role: builtins.str - """User's role in this workspace""" - def __init__( - self, - *, - id: builtins.str = ..., - name: builtins.str = ..., - slug: builtins.str = ..., - is_default: builtins.bool = ..., - role: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "id", b"id", "is_default", b"is_default", "name", b"name", "role", b"role", "slug", b"slug" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___WorkspaceProto: typing_extensions.TypeAlias = WorkspaceProto - -@typing.final -class ListWorkspacesRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - limit: builtins.int - """Maximum workspaces to return (default: 50)""" - offset: builtins.int - """Pagination offset""" - def __init__( - self, - *, - limit: builtins.int = ..., - offset: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "limit", b"limit", "offset", b"offset" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListWorkspacesRequest: typing_extensions.TypeAlias = ListWorkspacesRequest - -@typing.final -class ListWorkspacesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACES_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - """Total count""" - @property - def workspaces( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___WorkspaceProto - ]: - """User's workspaces""" - - def __init__( - self, - *, - workspaces: collections.abc.Iterable[Global___WorkspaceProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "total_count", b"total_count", "workspaces", b"workspaces" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListWorkspacesResponse: typing_extensions.TypeAlias = ListWorkspacesResponse - -@typing.final -class SwitchWorkspaceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace ID to switch to""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SwitchWorkspaceRequest: typing_extensions.TypeAlias = SwitchWorkspaceRequest - -@typing.final -class SwitchWorkspaceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - WORKSPACE_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - success: builtins.bool - """Whether switch succeeded""" - error_message: builtins.str - """Error message if failed""" - @property - def workspace(self) -> Global___WorkspaceProto: - """New current workspace info""" - - def __init__( - self, - *, - success: builtins.bool = ..., - workspace: Global___WorkspaceProto | None = ..., - error_message: builtins.str = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["workspace", b"workspace"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "error_message", b"error_message", "success", b"success", "workspace", b"workspace" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SwitchWorkspaceResponse: typing_extensions.TypeAlias = SwitchWorkspaceResponse - -@typing.final -class GetWorkspaceSettingsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace ID to fetch settings for""" - def __init__( - self, - *, - workspace_id: builtins.str = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetWorkspaceSettingsRequest: typing_extensions.TypeAlias = GetWorkspaceSettingsRequest - -@typing.final -class UpdateWorkspaceSettingsRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WORKSPACE_ID_FIELD_NUMBER: builtins.int - SETTINGS_FIELD_NUMBER: builtins.int - workspace_id: builtins.str - """Workspace ID to update settings for""" - @property - def settings(self) -> Global___WorkspaceSettingsProto: - """Updated settings (optional fields are merged)""" - - def __init__( - self, - *, - workspace_id: builtins.str = ..., - settings: Global___WorkspaceSettingsProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["settings", b"settings"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "settings", b"settings", "workspace_id", b"workspace_id" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateWorkspaceSettingsRequest: typing_extensions.TypeAlias = ( - UpdateWorkspaceSettingsRequest -) - -# ============================================================================= -# Task Management Messages (Bugfinder Sprint) -# ============================================================================= - -class _TaskStatusProto: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _TaskStatusProtoEnumTypeWrapper( - google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TaskStatusProto.ValueType], - builtins.type, -): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - TASK_STATUS_UNSPECIFIED: _TaskStatusProto.ValueType # 0 - TASK_STATUS_OPEN: _TaskStatusProto.ValueType # 1 - TASK_STATUS_DONE: _TaskStatusProto.ValueType # 2 - TASK_STATUS_DISMISSED: _TaskStatusProto.ValueType # 3 - -class TaskStatusProto(_TaskStatusProto, metaclass=_TaskStatusProtoEnumTypeWrapper): - """Task status enum""" - -TASK_STATUS_UNSPECIFIED: TaskStatusProto.ValueType # 0 -TASK_STATUS_OPEN: TaskStatusProto.ValueType # 1 -TASK_STATUS_DONE: TaskStatusProto.ValueType # 2 -TASK_STATUS_DISMISSED: TaskStatusProto.ValueType # 3 -Global___TaskStatusProto: typing_extensions.TypeAlias = TaskStatusProto - -@typing.final -class TaskProto(google.protobuf.message.Message): - """Task entity""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - MEETING_ID_FIELD_NUMBER: builtins.int - ACTION_ITEM_ID_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - ASSIGNEE_PERSON_ID_FIELD_NUMBER: builtins.int - DUE_DATE_FIELD_NUMBER: builtins.int - PRIORITY_FIELD_NUMBER: builtins.int - COMPLETED_AT_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - UPDATED_AT_FIELD_NUMBER: builtins.int - id: builtins.str - meeting_id: builtins.str - action_item_id: builtins.int - text: builtins.str - status: Global___TaskStatusProto.ValueType - assignee_person_id: builtins.str - due_date: builtins.float - priority: builtins.int - completed_at: builtins.float - created_at: builtins.float - updated_at: builtins.float - def __init__( - self, - *, - id: builtins.str = ..., - meeting_id: builtins.str = ..., - action_item_id: builtins.int = ..., - text: builtins.str = ..., - status: Global___TaskStatusProto.ValueType = ..., - assignee_person_id: builtins.str = ..., - due_date: builtins.float = ..., - priority: builtins.int = ..., - completed_at: builtins.float = ..., - created_at: builtins.float = ..., - updated_at: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "action_item_id", - b"action_item_id", - "assignee_person_id", - b"assignee_person_id", - "completed_at", - b"completed_at", - "created_at", - b"created_at", - "due_date", - b"due_date", - "id", - b"id", - "meeting_id", - b"meeting_id", - "priority", - b"priority", - "status", - b"status", - "text", - b"text", - "updated_at", - b"updated_at", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___TaskProto: typing_extensions.TypeAlias = TaskProto - -@typing.final -class TaskWithMeetingProto(google.protobuf.message.Message): - """Task with meeting context""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TASK_FIELD_NUMBER: builtins.int - MEETING_TITLE_FIELD_NUMBER: builtins.int - MEETING_CREATED_AT_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - meeting_title: builtins.str - meeting_created_at: builtins.float - project_id: builtins.str - @property - def task(self) -> Global___TaskProto: ... - def __init__( - self, - *, - task: Global___TaskProto | None = ..., - meeting_title: builtins.str = ..., - meeting_created_at: builtins.float = ..., - project_id: builtins.str = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["task", b"task"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "meeting_created_at", - b"meeting_created_at", - "meeting_title", - b"meeting_title", - "project_id", - b"project_id", - "task", - b"task", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___TaskWithMeetingProto: typing_extensions.TypeAlias = TaskWithMeetingProto - -@typing.final -class ListTasksRequest(google.protobuf.message.Message): - """List tasks request""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATUSES_FIELD_NUMBER: builtins.int - LIMIT_FIELD_NUMBER: builtins.int - OFFSET_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - PROJECT_IDS_FIELD_NUMBER: builtins.int - MEETING_ID_FIELD_NUMBER: builtins.int - limit: builtins.int - offset: builtins.int - project_id: builtins.str - meeting_id: builtins.str - @property - def statuses( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[ - Global___TaskStatusProto.ValueType - ]: ... - @property - def project_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - def __init__( - self, - *, - statuses: collections.abc.Iterable[Global___TaskStatusProto.ValueType] | None = ..., - limit: builtins.int = ..., - offset: builtins.int = ..., - project_id: builtins.str | None = ..., - project_ids: collections.abc.Iterable[builtins.str] | None = ..., - meeting_id: builtins.str | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_meeting_id", - b"_meeting_id", - "_project_id", - b"_project_id", - "meeting_id", - b"meeting_id", - "project_id", - b"project_id", - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_meeting_id", - b"_meeting_id", - "_project_id", - b"_project_id", - "limit", - b"limit", - "meeting_id", - b"meeting_id", - "offset", - b"offset", - "project_id", - b"project_id", - "project_ids", - b"project_ids", - "statuses", - b"statuses", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__meeting_id: typing_extensions.TypeAlias = typing.Literal["meeting_id"] - _WhichOneofArgType__meeting_id: typing_extensions.TypeAlias = typing.Literal[ - "_meeting_id", b"_meeting_id" - ] - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__meeting_id - ) -> _WhichOneofReturnType__meeting_id | None: ... - @typing.overload - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___ListTasksRequest: typing_extensions.TypeAlias = ListTasksRequest - -@typing.final -class ListTasksResponse(google.protobuf.message.Message): - """List tasks response""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TASKS_FIELD_NUMBER: builtins.int - TOTAL_COUNT_FIELD_NUMBER: builtins.int - total_count: builtins.int - @property - def tasks( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___TaskWithMeetingProto - ]: ... - def __init__( - self, - *, - tasks: collections.abc.Iterable[Global___TaskWithMeetingProto] | None = ..., - total_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "tasks", b"tasks", "total_count", b"total_count" - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListTasksResponse: typing_extensions.TypeAlias = ListTasksResponse - -@typing.final -class UpdateTaskRequest(google.protobuf.message.Message): - """Update task request""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TASK_ID_FIELD_NUMBER: builtins.int - TEXT_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - ASSIGNEE_PERSON_ID_FIELD_NUMBER: builtins.int - DUE_DATE_FIELD_NUMBER: builtins.int - PRIORITY_FIELD_NUMBER: builtins.int - task_id: builtins.str - text: builtins.str - status: Global___TaskStatusProto.ValueType - assignee_person_id: builtins.str - due_date: builtins.float - priority: builtins.int - def __init__( - self, - *, - task_id: builtins.str = ..., - text: builtins.str = ..., - status: Global___TaskStatusProto.ValueType = ..., - assignee_person_id: builtins.str = ..., - due_date: builtins.float = ..., - priority: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "assignee_person_id", - b"assignee_person_id", - "due_date", - b"due_date", - "priority", - b"priority", - "status", - b"status", - "task_id", - b"task_id", - "text", - b"text", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateTaskRequest: typing_extensions.TypeAlias = UpdateTaskRequest - -@typing.final -class UpdateTaskResponse(google.protobuf.message.Message): - """Update task response""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TASK_FIELD_NUMBER: builtins.int - @property - def task(self) -> Global___TaskProto: ... - def __init__( - self, - *, - task: Global___TaskProto | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["task", b"task"] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["task", b"task"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___UpdateTaskResponse: typing_extensions.TypeAlias = UpdateTaskResponse - -# ============================================================================= -# Analytics Messages (Bugfinder Sprint) -# ============================================================================= - -@typing.final -class GetAnalyticsOverviewRequest(google.protobuf.message.Message): - """Get analytics overview request""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - PROJECT_IDS_FIELD_NUMBER: builtins.int - start_time: builtins.float - end_time: builtins.float - project_id: builtins.str - @property - def project_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - def __init__( - self, - *, - start_time: builtins.float = ..., - end_time: builtins.float = ..., - project_id: builtins.str | None = ..., - project_ids: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project_id", b"project_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "end_time", - b"end_time", - "project_id", - b"project_id", - "project_ids", - b"project_ids", - "start_time", - b"start_time", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___GetAnalyticsOverviewRequest: typing_extensions.TypeAlias = GetAnalyticsOverviewRequest - -@typing.final -class DailyMeetingStatsProto(google.protobuf.message.Message): - """Daily meeting statistics""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATE_FIELD_NUMBER: builtins.int - MEETINGS_FIELD_NUMBER: builtins.int - TOTAL_DURATION_FIELD_NUMBER: builtins.int - WORD_COUNT_FIELD_NUMBER: builtins.int - date: builtins.str - meetings: builtins.int - total_duration: builtins.float - word_count: builtins.int - def __init__( - self, - *, - date: builtins.str = ..., - meetings: builtins.int = ..., - total_duration: builtins.float = ..., - word_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "date", - b"date", - "meetings", - b"meetings", - "total_duration", - b"total_duration", - "word_count", - b"word_count", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___DailyMeetingStatsProto: typing_extensions.TypeAlias = DailyMeetingStatsProto - -@typing.final -class GetAnalyticsOverviewResponse(google.protobuf.message.Message): - """Get analytics overview response""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DAILY_FIELD_NUMBER: builtins.int - TOTAL_MEETINGS_FIELD_NUMBER: builtins.int - TOTAL_DURATION_FIELD_NUMBER: builtins.int - TOTAL_WORDS_FIELD_NUMBER: builtins.int - TOTAL_SEGMENTS_FIELD_NUMBER: builtins.int - SPEAKER_COUNT_FIELD_NUMBER: builtins.int - total_meetings: builtins.int - total_duration: builtins.float - total_words: builtins.int - total_segments: builtins.int - speaker_count: builtins.int - @property - def daily( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___DailyMeetingStatsProto - ]: ... - def __init__( - self, - *, - daily: collections.abc.Iterable[Global___DailyMeetingStatsProto] | None = ..., - total_meetings: builtins.int = ..., - total_duration: builtins.float = ..., - total_words: builtins.int = ..., - total_segments: builtins.int = ..., - speaker_count: builtins.int = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "daily", - b"daily", - "speaker_count", - b"speaker_count", - "total_duration", - b"total_duration", - "total_meetings", - b"total_meetings", - "total_segments", - b"total_segments", - "total_words", - b"total_words", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___GetAnalyticsOverviewResponse: typing_extensions.TypeAlias = GetAnalyticsOverviewResponse - -@typing.final -class SpeakerStatProto(google.protobuf.message.Message): - """Speaker statistics""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SPEAKER_ID_FIELD_NUMBER: builtins.int - DISPLAY_NAME_FIELD_NUMBER: builtins.int - TOTAL_TIME_FIELD_NUMBER: builtins.int - SEGMENT_COUNT_FIELD_NUMBER: builtins.int - MEETING_COUNT_FIELD_NUMBER: builtins.int - AVG_CONFIDENCE_FIELD_NUMBER: builtins.int - speaker_id: builtins.str - display_name: builtins.str - total_time: builtins.float - segment_count: builtins.int - meeting_count: builtins.int - avg_confidence: builtins.float - def __init__( - self, - *, - speaker_id: builtins.str = ..., - display_name: builtins.str = ..., - total_time: builtins.float = ..., - segment_count: builtins.int = ..., - meeting_count: builtins.int = ..., - avg_confidence: builtins.float = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "avg_confidence", - b"avg_confidence", - "display_name", - b"display_name", - "meeting_count", - b"meeting_count", - "segment_count", - b"segment_count", - "speaker_id", - b"speaker_id", - "total_time", - b"total_time", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___SpeakerStatProto: typing_extensions.TypeAlias = SpeakerStatProto - -@typing.final -class ListSpeakerStatsRequest(google.protobuf.message.Message): - """List speaker stats request""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - START_TIME_FIELD_NUMBER: builtins.int - END_TIME_FIELD_NUMBER: builtins.int - PROJECT_ID_FIELD_NUMBER: builtins.int - PROJECT_IDS_FIELD_NUMBER: builtins.int - start_time: builtins.float - end_time: builtins.float - project_id: builtins.str - @property - def project_ids( - self, - ) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - def __init__( - self, - *, - start_time: builtins.float = ..., - end_time: builtins.float = ..., - project_id: builtins.str | None = ..., - project_ids: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id", "project_id", b"project_id" - ] - def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", - b"_project_id", - "end_time", - b"end_time", - "project_id", - b"project_id", - "project_ids", - b"project_ids", - "start_time", - b"start_time", - ] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - _WhichOneofReturnType__project_id: typing_extensions.TypeAlias = typing.Literal["project_id"] - _WhichOneofArgType__project_id: typing_extensions.TypeAlias = typing.Literal[ - "_project_id", b"_project_id" - ] - def WhichOneof( - self, oneof_group: _WhichOneofArgType__project_id - ) -> _WhichOneofReturnType__project_id | None: ... - -Global___ListSpeakerStatsRequest: typing_extensions.TypeAlias = ListSpeakerStatsRequest - -@typing.final -class ListSpeakerStatsResponse(google.protobuf.message.Message): - """List speaker stats response""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SPEAKERS_FIELD_NUMBER: builtins.int - @property - def speakers( - self, - ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[ - Global___SpeakerStatProto - ]: ... - def __init__( - self, - *, - speakers: collections.abc.Iterable[Global___SpeakerStatProto] | None = ..., - ) -> None: ... - _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["speakers", b"speakers"] - def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - -Global___ListSpeakerStatsResponse: typing_extensions.TypeAlias = ListSpeakerStatsResponse +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class UpdateType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + UPDATE_TYPE_UNSPECIFIED: _ClassVar[UpdateType] + UPDATE_TYPE_PARTIAL: _ClassVar[UpdateType] + UPDATE_TYPE_FINAL: _ClassVar[UpdateType] + UPDATE_TYPE_VAD_START: _ClassVar[UpdateType] + UPDATE_TYPE_VAD_END: _ClassVar[UpdateType] + +class MeetingState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + MEETING_STATE_UNSPECIFIED: _ClassVar[MeetingState] + MEETING_STATE_CREATED: _ClassVar[MeetingState] + MEETING_STATE_RECORDING: _ClassVar[MeetingState] + MEETING_STATE_STOPPED: _ClassVar[MeetingState] + MEETING_STATE_COMPLETED: _ClassVar[MeetingState] + MEETING_STATE_ERROR: _ClassVar[MeetingState] + +class SortOrder(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SORT_ORDER_UNSPECIFIED: _ClassVar[SortOrder] + SORT_ORDER_CREATED_DESC: _ClassVar[SortOrder] + SORT_ORDER_CREATED_ASC: _ClassVar[SortOrder] + +class Priority(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PRIORITY_UNSPECIFIED: _ClassVar[Priority] + PRIORITY_LOW: _ClassVar[Priority] + PRIORITY_MEDIUM: _ClassVar[Priority] + PRIORITY_HIGH: _ClassVar[Priority] + +class AsrDevice(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ASR_DEVICE_UNSPECIFIED: _ClassVar[AsrDevice] + ASR_DEVICE_CPU: _ClassVar[AsrDevice] + ASR_DEVICE_CUDA: _ClassVar[AsrDevice] + ASR_DEVICE_ROCM: _ClassVar[AsrDevice] + +class AsrComputeType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ASR_COMPUTE_TYPE_UNSPECIFIED: _ClassVar[AsrComputeType] + ASR_COMPUTE_TYPE_INT8: _ClassVar[AsrComputeType] + ASR_COMPUTE_TYPE_FLOAT16: _ClassVar[AsrComputeType] + ASR_COMPUTE_TYPE_FLOAT32: _ClassVar[AsrComputeType] + +class AnnotationType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ANNOTATION_TYPE_UNSPECIFIED: _ClassVar[AnnotationType] + ANNOTATION_TYPE_ACTION_ITEM: _ClassVar[AnnotationType] + ANNOTATION_TYPE_DECISION: _ClassVar[AnnotationType] + ANNOTATION_TYPE_NOTE: _ClassVar[AnnotationType] + ANNOTATION_TYPE_RISK: _ClassVar[AnnotationType] + +class ExportFormat(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + EXPORT_FORMAT_UNSPECIFIED: _ClassVar[ExportFormat] + EXPORT_FORMAT_MARKDOWN: _ClassVar[ExportFormat] + EXPORT_FORMAT_HTML: _ClassVar[ExportFormat] + EXPORT_FORMAT_PDF: _ClassVar[ExportFormat] + +class JobStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + JOB_STATUS_UNSPECIFIED: _ClassVar[JobStatus] + JOB_STATUS_QUEUED: _ClassVar[JobStatus] + JOB_STATUS_RUNNING: _ClassVar[JobStatus] + JOB_STATUS_COMPLETED: _ClassVar[JobStatus] + JOB_STATUS_FAILED: _ClassVar[JobStatus] + JOB_STATUS_CANCELLED: _ClassVar[JobStatus] + +class SyncErrorCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SYNC_ERROR_CODE_UNSPECIFIED: _ClassVar[SyncErrorCode] + SYNC_ERROR_CODE_AUTH_REQUIRED: _ClassVar[SyncErrorCode] + SYNC_ERROR_CODE_PROVIDER_ERROR: _ClassVar[SyncErrorCode] + SYNC_ERROR_CODE_INTERNAL_ERROR: _ClassVar[SyncErrorCode] + SYNC_ERROR_CODE_UNKNOWN: _ClassVar[SyncErrorCode] + +class ProcessingStepStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PROCESSING_STEP_UNSPECIFIED: _ClassVar[ProcessingStepStatus] + PROCESSING_STEP_PENDING: _ClassVar[ProcessingStepStatus] + PROCESSING_STEP_RUNNING: _ClassVar[ProcessingStepStatus] + PROCESSING_STEP_COMPLETED: _ClassVar[ProcessingStepStatus] + PROCESSING_STEP_FAILED: _ClassVar[ProcessingStepStatus] + PROCESSING_STEP_SKIPPED: _ClassVar[ProcessingStepStatus] + +class ProjectRoleProto(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PROJECT_ROLE_UNSPECIFIED: _ClassVar[ProjectRoleProto] + PROJECT_ROLE_VIEWER: _ClassVar[ProjectRoleProto] + PROJECT_ROLE_EDITOR: _ClassVar[ProjectRoleProto] + PROJECT_ROLE_ADMIN: _ClassVar[ProjectRoleProto] + +class TaskStatusProto(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TASK_STATUS_UNSPECIFIED: _ClassVar[TaskStatusProto] + TASK_STATUS_OPEN: _ClassVar[TaskStatusProto] + TASK_STATUS_DONE: _ClassVar[TaskStatusProto] + TASK_STATUS_DISMISSED: _ClassVar[TaskStatusProto] +UPDATE_TYPE_UNSPECIFIED: UpdateType +UPDATE_TYPE_PARTIAL: UpdateType +UPDATE_TYPE_FINAL: UpdateType +UPDATE_TYPE_VAD_START: UpdateType +UPDATE_TYPE_VAD_END: UpdateType +MEETING_STATE_UNSPECIFIED: MeetingState +MEETING_STATE_CREATED: MeetingState +MEETING_STATE_RECORDING: MeetingState +MEETING_STATE_STOPPED: MeetingState +MEETING_STATE_COMPLETED: MeetingState +MEETING_STATE_ERROR: MeetingState +SORT_ORDER_UNSPECIFIED: SortOrder +SORT_ORDER_CREATED_DESC: SortOrder +SORT_ORDER_CREATED_ASC: SortOrder +PRIORITY_UNSPECIFIED: Priority +PRIORITY_LOW: Priority +PRIORITY_MEDIUM: Priority +PRIORITY_HIGH: Priority +ASR_DEVICE_UNSPECIFIED: AsrDevice +ASR_DEVICE_CPU: AsrDevice +ASR_DEVICE_CUDA: AsrDevice +ASR_DEVICE_ROCM: AsrDevice +ASR_COMPUTE_TYPE_UNSPECIFIED: AsrComputeType +ASR_COMPUTE_TYPE_INT8: AsrComputeType +ASR_COMPUTE_TYPE_FLOAT16: AsrComputeType +ASR_COMPUTE_TYPE_FLOAT32: AsrComputeType +ANNOTATION_TYPE_UNSPECIFIED: AnnotationType +ANNOTATION_TYPE_ACTION_ITEM: AnnotationType +ANNOTATION_TYPE_DECISION: AnnotationType +ANNOTATION_TYPE_NOTE: AnnotationType +ANNOTATION_TYPE_RISK: AnnotationType +EXPORT_FORMAT_UNSPECIFIED: ExportFormat +EXPORT_FORMAT_MARKDOWN: ExportFormat +EXPORT_FORMAT_HTML: ExportFormat +EXPORT_FORMAT_PDF: ExportFormat +JOB_STATUS_UNSPECIFIED: JobStatus +JOB_STATUS_QUEUED: JobStatus +JOB_STATUS_RUNNING: JobStatus +JOB_STATUS_COMPLETED: JobStatus +JOB_STATUS_FAILED: JobStatus +JOB_STATUS_CANCELLED: JobStatus +SYNC_ERROR_CODE_UNSPECIFIED: SyncErrorCode +SYNC_ERROR_CODE_AUTH_REQUIRED: SyncErrorCode +SYNC_ERROR_CODE_PROVIDER_ERROR: SyncErrorCode +SYNC_ERROR_CODE_INTERNAL_ERROR: SyncErrorCode +SYNC_ERROR_CODE_UNKNOWN: SyncErrorCode +PROCESSING_STEP_UNSPECIFIED: ProcessingStepStatus +PROCESSING_STEP_PENDING: ProcessingStepStatus +PROCESSING_STEP_RUNNING: ProcessingStepStatus +PROCESSING_STEP_COMPLETED: ProcessingStepStatus +PROCESSING_STEP_FAILED: ProcessingStepStatus +PROCESSING_STEP_SKIPPED: ProcessingStepStatus +PROJECT_ROLE_UNSPECIFIED: ProjectRoleProto +PROJECT_ROLE_VIEWER: ProjectRoleProto +PROJECT_ROLE_EDITOR: ProjectRoleProto +PROJECT_ROLE_ADMIN: ProjectRoleProto +TASK_STATUS_UNSPECIFIED: TaskStatusProto +TASK_STATUS_OPEN: TaskStatusProto +TASK_STATUS_DONE: TaskStatusProto +TASK_STATUS_DISMISSED: TaskStatusProto + +class AudioChunk(_message.Message): + __slots__ = ("meeting_id", "audio_data", "timestamp", "sample_rate", "channels", "chunk_sequence") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + AUDIO_DATA_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + SAMPLE_RATE_FIELD_NUMBER: _ClassVar[int] + CHANNELS_FIELD_NUMBER: _ClassVar[int] + CHUNK_SEQUENCE_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + audio_data: bytes + timestamp: float + sample_rate: int + channels: int + chunk_sequence: int + def __init__(self, meeting_id: _Optional[str] = ..., audio_data: _Optional[bytes] = ..., timestamp: _Optional[float] = ..., sample_rate: _Optional[int] = ..., channels: _Optional[int] = ..., chunk_sequence: _Optional[int] = ...) -> None: ... + +class CongestionInfo(_message.Message): + __slots__ = ("processing_delay_ms", "queue_depth", "throttle_recommended") + PROCESSING_DELAY_MS_FIELD_NUMBER: _ClassVar[int] + QUEUE_DEPTH_FIELD_NUMBER: _ClassVar[int] + THROTTLE_RECOMMENDED_FIELD_NUMBER: _ClassVar[int] + processing_delay_ms: int + queue_depth: int + throttle_recommended: bool + def __init__(self, processing_delay_ms: _Optional[int] = ..., queue_depth: _Optional[int] = ..., throttle_recommended: bool = ...) -> None: ... + +class TranscriptUpdate(_message.Message): + __slots__ = ("meeting_id", "update_type", "partial_text", "segment", "server_timestamp", "ack_sequence", "congestion") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + UPDATE_TYPE_FIELD_NUMBER: _ClassVar[int] + PARTIAL_TEXT_FIELD_NUMBER: _ClassVar[int] + SEGMENT_FIELD_NUMBER: _ClassVar[int] + SERVER_TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + ACK_SEQUENCE_FIELD_NUMBER: _ClassVar[int] + CONGESTION_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + update_type: UpdateType + partial_text: str + segment: FinalSegment + server_timestamp: float + ack_sequence: int + congestion: CongestionInfo + def __init__(self, meeting_id: _Optional[str] = ..., update_type: _Optional[_Union[UpdateType, str]] = ..., partial_text: _Optional[str] = ..., segment: _Optional[_Union[FinalSegment, _Mapping]] = ..., server_timestamp: _Optional[float] = ..., ack_sequence: _Optional[int] = ..., congestion: _Optional[_Union[CongestionInfo, _Mapping]] = ...) -> None: ... + +class FinalSegment(_message.Message): + __slots__ = ("segment_id", "text", "start_time", "end_time", "words", "language", "language_confidence", "avg_logprob", "no_speech_prob", "speaker_id", "speaker_confidence") + SEGMENT_ID_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + WORDS_FIELD_NUMBER: _ClassVar[int] + LANGUAGE_FIELD_NUMBER: _ClassVar[int] + LANGUAGE_CONFIDENCE_FIELD_NUMBER: _ClassVar[int] + AVG_LOGPROB_FIELD_NUMBER: _ClassVar[int] + NO_SPEECH_PROB_FIELD_NUMBER: _ClassVar[int] + SPEAKER_ID_FIELD_NUMBER: _ClassVar[int] + SPEAKER_CONFIDENCE_FIELD_NUMBER: _ClassVar[int] + segment_id: int + text: str + start_time: float + end_time: float + words: _containers.RepeatedCompositeFieldContainer[WordTiming] + language: str + language_confidence: float + avg_logprob: float + no_speech_prob: float + speaker_id: str + speaker_confidence: float + def __init__(self, segment_id: _Optional[int] = ..., text: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., words: _Optional[_Iterable[_Union[WordTiming, _Mapping]]] = ..., language: _Optional[str] = ..., language_confidence: _Optional[float] = ..., avg_logprob: _Optional[float] = ..., no_speech_prob: _Optional[float] = ..., speaker_id: _Optional[str] = ..., speaker_confidence: _Optional[float] = ...) -> None: ... + +class WordTiming(_message.Message): + __slots__ = ("word", "start_time", "end_time", "probability") + WORD_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + PROBABILITY_FIELD_NUMBER: _ClassVar[int] + word: str + start_time: float + end_time: float + probability: float + def __init__(self, word: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., probability: _Optional[float] = ...) -> None: ... + +class Meeting(_message.Message): + __slots__ = ("id", "title", "state", "created_at", "started_at", "ended_at", "duration_seconds", "segments", "summary", "metadata", "project_id", "processing_status") + class MetadataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + STARTED_AT_FIELD_NUMBER: _ClassVar[int] + ENDED_AT_FIELD_NUMBER: _ClassVar[int] + DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int] + SEGMENTS_FIELD_NUMBER: _ClassVar[int] + SUMMARY_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROCESSING_STATUS_FIELD_NUMBER: _ClassVar[int] + id: str + title: str + state: MeetingState + created_at: float + started_at: float + ended_at: float + duration_seconds: float + segments: _containers.RepeatedCompositeFieldContainer[FinalSegment] + summary: Summary + metadata: _containers.ScalarMap[str, str] + project_id: str + processing_status: ProcessingStatus + def __init__(self, id: _Optional[str] = ..., title: _Optional[str] = ..., state: _Optional[_Union[MeetingState, str]] = ..., created_at: _Optional[float] = ..., started_at: _Optional[float] = ..., ended_at: _Optional[float] = ..., duration_seconds: _Optional[float] = ..., segments: _Optional[_Iterable[_Union[FinalSegment, _Mapping]]] = ..., summary: _Optional[_Union[Summary, _Mapping]] = ..., metadata: _Optional[_Mapping[str, str]] = ..., project_id: _Optional[str] = ..., processing_status: _Optional[_Union[ProcessingStatus, _Mapping]] = ...) -> None: ... + +class CreateMeetingRequest(_message.Message): + __slots__ = ("title", "metadata", "project_id") + class MetadataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + TITLE_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + title: str + metadata: _containers.ScalarMap[str, str] + project_id: str + def __init__(self, title: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ..., project_id: _Optional[str] = ...) -> None: ... + +class StopMeetingRequest(_message.Message): + __slots__ = ("meeting_id",) + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + def __init__(self, meeting_id: _Optional[str] = ...) -> None: ... + +class ListMeetingsRequest(_message.Message): + __slots__ = ("states", "limit", "offset", "sort_order", "project_id", "project_ids", "include_segments") + STATES_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + SORT_ORDER_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_IDS_FIELD_NUMBER: _ClassVar[int] + INCLUDE_SEGMENTS_FIELD_NUMBER: _ClassVar[int] + states: _containers.RepeatedScalarFieldContainer[MeetingState] + limit: int + offset: int + sort_order: SortOrder + project_id: str + project_ids: _containers.RepeatedScalarFieldContainer[str] + include_segments: bool + def __init__(self, states: _Optional[_Iterable[_Union[MeetingState, str]]] = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ..., sort_order: _Optional[_Union[SortOrder, str]] = ..., project_id: _Optional[str] = ..., project_ids: _Optional[_Iterable[str]] = ..., include_segments: bool = ...) -> None: ... + +class ListMeetingsResponse(_message.Message): + __slots__ = ("meetings", "total_count") + MEETINGS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + meetings: _containers.RepeatedCompositeFieldContainer[Meeting] + total_count: int + def __init__(self, meetings: _Optional[_Iterable[_Union[Meeting, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GetMeetingRequest(_message.Message): + __slots__ = ("meeting_id", "include_segments", "include_summary") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + INCLUDE_SEGMENTS_FIELD_NUMBER: _ClassVar[int] + INCLUDE_SUMMARY_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + include_segments: bool + include_summary: bool + def __init__(self, meeting_id: _Optional[str] = ..., include_segments: bool = ..., include_summary: bool = ...) -> None: ... + +class DeleteMeetingRequest(_message.Message): + __slots__ = ("meeting_id",) + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + def __init__(self, meeting_id: _Optional[str] = ...) -> None: ... + +class DeleteMeetingResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class Summary(_message.Message): + __slots__ = ("meeting_id", "executive_summary", "key_points", "action_items", "generated_at", "model_version") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + EXECUTIVE_SUMMARY_FIELD_NUMBER: _ClassVar[int] + KEY_POINTS_FIELD_NUMBER: _ClassVar[int] + ACTION_ITEMS_FIELD_NUMBER: _ClassVar[int] + GENERATED_AT_FIELD_NUMBER: _ClassVar[int] + MODEL_VERSION_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + executive_summary: str + key_points: _containers.RepeatedCompositeFieldContainer[KeyPoint] + action_items: _containers.RepeatedCompositeFieldContainer[ActionItem] + generated_at: float + model_version: str + def __init__(self, meeting_id: _Optional[str] = ..., executive_summary: _Optional[str] = ..., key_points: _Optional[_Iterable[_Union[KeyPoint, _Mapping]]] = ..., action_items: _Optional[_Iterable[_Union[ActionItem, _Mapping]]] = ..., generated_at: _Optional[float] = ..., model_version: _Optional[str] = ...) -> None: ... + +class KeyPoint(_message.Message): + __slots__ = ("text", "segment_ids", "start_time", "end_time") + TEXT_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + text: str + segment_ids: _containers.RepeatedScalarFieldContainer[int] + start_time: float + end_time: float + def __init__(self, text: _Optional[str] = ..., segment_ids: _Optional[_Iterable[int]] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ...) -> None: ... + +class ActionItem(_message.Message): + __slots__ = ("text", "assignee", "due_date", "priority", "segment_ids") + TEXT_FIELD_NUMBER: _ClassVar[int] + ASSIGNEE_FIELD_NUMBER: _ClassVar[int] + DUE_DATE_FIELD_NUMBER: _ClassVar[int] + PRIORITY_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + text: str + assignee: str + due_date: float + priority: Priority + segment_ids: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, text: _Optional[str] = ..., assignee: _Optional[str] = ..., due_date: _Optional[float] = ..., priority: _Optional[_Union[Priority, str]] = ..., segment_ids: _Optional[_Iterable[int]] = ...) -> None: ... + +class SummarizationOptions(_message.Message): + __slots__ = ("tone", "format", "verbosity", "template_id") + TONE_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + VERBOSITY_FIELD_NUMBER: _ClassVar[int] + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + tone: str + format: str + verbosity: str + template_id: str + def __init__(self, tone: _Optional[str] = ..., format: _Optional[str] = ..., verbosity: _Optional[str] = ..., template_id: _Optional[str] = ...) -> None: ... + +class GenerateSummaryRequest(_message.Message): + __slots__ = ("meeting_id", "force_regenerate", "options") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + FORCE_REGENERATE_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + force_regenerate: bool + options: SummarizationOptions + def __init__(self, meeting_id: _Optional[str] = ..., force_regenerate: bool = ..., options: _Optional[_Union[SummarizationOptions, _Mapping]] = ...) -> None: ... + +class SummarizationTemplateProto(_message.Message): + __slots__ = ("id", "workspace_id", "name", "description", "is_system", "is_archived", "current_version_id", "created_at", "updated_at", "created_by", "updated_by") + ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + IS_SYSTEM_FIELD_NUMBER: _ClassVar[int] + IS_ARCHIVED_FIELD_NUMBER: _ClassVar[int] + CURRENT_VERSION_ID_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + CREATED_BY_FIELD_NUMBER: _ClassVar[int] + UPDATED_BY_FIELD_NUMBER: _ClassVar[int] + id: str + workspace_id: str + name: str + description: str + is_system: bool + is_archived: bool + current_version_id: str + created_at: int + updated_at: int + created_by: str + updated_by: str + def __init__(self, id: _Optional[str] = ..., workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., is_system: bool = ..., is_archived: bool = ..., current_version_id: _Optional[str] = ..., created_at: _Optional[int] = ..., updated_at: _Optional[int] = ..., created_by: _Optional[str] = ..., updated_by: _Optional[str] = ...) -> None: ... + +class SummarizationTemplateVersionProto(_message.Message): + __slots__ = ("id", "template_id", "version_number", "content", "change_note", "created_at", "created_by") + ID_FIELD_NUMBER: _ClassVar[int] + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + VERSION_NUMBER_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + CHANGE_NOTE_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + CREATED_BY_FIELD_NUMBER: _ClassVar[int] + id: str + template_id: str + version_number: int + content: str + change_note: str + created_at: int + created_by: str + def __init__(self, id: _Optional[str] = ..., template_id: _Optional[str] = ..., version_number: _Optional[int] = ..., content: _Optional[str] = ..., change_note: _Optional[str] = ..., created_at: _Optional[int] = ..., created_by: _Optional[str] = ...) -> None: ... + +class ListSummarizationTemplatesRequest(_message.Message): + __slots__ = ("workspace_id", "include_system", "include_archived", "limit", "offset") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + INCLUDE_SYSTEM_FIELD_NUMBER: _ClassVar[int] + INCLUDE_ARCHIVED_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + include_system: bool + include_archived: bool + limit: int + offset: int + def __init__(self, workspace_id: _Optional[str] = ..., include_system: bool = ..., include_archived: bool = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListSummarizationTemplatesResponse(_message.Message): + __slots__ = ("templates", "total_count") + TEMPLATES_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + templates: _containers.RepeatedCompositeFieldContainer[SummarizationTemplateProto] + total_count: int + def __init__(self, templates: _Optional[_Iterable[_Union[SummarizationTemplateProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GetSummarizationTemplateRequest(_message.Message): + __slots__ = ("template_id", "include_current_version") + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + INCLUDE_CURRENT_VERSION_FIELD_NUMBER: _ClassVar[int] + template_id: str + include_current_version: bool + def __init__(self, template_id: _Optional[str] = ..., include_current_version: bool = ...) -> None: ... + +class GetSummarizationTemplateResponse(_message.Message): + __slots__ = ("template", "current_version") + TEMPLATE_FIELD_NUMBER: _ClassVar[int] + CURRENT_VERSION_FIELD_NUMBER: _ClassVar[int] + template: SummarizationTemplateProto + current_version: SummarizationTemplateVersionProto + def __init__(self, template: _Optional[_Union[SummarizationTemplateProto, _Mapping]] = ..., current_version: _Optional[_Union[SummarizationTemplateVersionProto, _Mapping]] = ...) -> None: ... + +class SummarizationTemplateMutationResponse(_message.Message): + __slots__ = ("template", "version") + TEMPLATE_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + template: SummarizationTemplateProto + version: SummarizationTemplateVersionProto + def __init__(self, template: _Optional[_Union[SummarizationTemplateProto, _Mapping]] = ..., version: _Optional[_Union[SummarizationTemplateVersionProto, _Mapping]] = ...) -> None: ... + +class CreateSummarizationTemplateRequest(_message.Message): + __slots__ = ("workspace_id", "name", "description", "content", "change_note") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + CHANGE_NOTE_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + name: str + description: str + content: str + change_note: str + def __init__(self, workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., content: _Optional[str] = ..., change_note: _Optional[str] = ...) -> None: ... + +class UpdateSummarizationTemplateRequest(_message.Message): + __slots__ = ("template_id", "name", "description", "content", "change_note") + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + CHANGE_NOTE_FIELD_NUMBER: _ClassVar[int] + template_id: str + name: str + description: str + content: str + change_note: str + def __init__(self, template_id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., content: _Optional[str] = ..., change_note: _Optional[str] = ...) -> None: ... + +class ArchiveSummarizationTemplateRequest(_message.Message): + __slots__ = ("template_id",) + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + template_id: str + def __init__(self, template_id: _Optional[str] = ...) -> None: ... + +class ListSummarizationTemplateVersionsRequest(_message.Message): + __slots__ = ("template_id", "limit", "offset") + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + template_id: str + limit: int + offset: int + def __init__(self, template_id: _Optional[str] = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListSummarizationTemplateVersionsResponse(_message.Message): + __slots__ = ("versions", "total_count") + VERSIONS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + versions: _containers.RepeatedCompositeFieldContainer[SummarizationTemplateVersionProto] + total_count: int + def __init__(self, versions: _Optional[_Iterable[_Union[SummarizationTemplateVersionProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class RestoreSummarizationTemplateVersionRequest(_message.Message): + __slots__ = ("template_id", "version_id") + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + VERSION_ID_FIELD_NUMBER: _ClassVar[int] + template_id: str + version_id: str + def __init__(self, template_id: _Optional[str] = ..., version_id: _Optional[str] = ...) -> None: ... + +class ServerInfoRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ServerInfo(_message.Message): + __slots__ = ("version", "asr_model", "asr_ready", "supported_sample_rates", "max_chunk_size", "uptime_seconds", "active_meetings", "diarization_enabled", "diarization_ready", "state_version", "system_ram_total_bytes", "system_ram_available_bytes", "gpu_vram_total_bytes", "gpu_vram_available_bytes") + VERSION_FIELD_NUMBER: _ClassVar[int] + ASR_MODEL_FIELD_NUMBER: _ClassVar[int] + ASR_READY_FIELD_NUMBER: _ClassVar[int] + SUPPORTED_SAMPLE_RATES_FIELD_NUMBER: _ClassVar[int] + MAX_CHUNK_SIZE_FIELD_NUMBER: _ClassVar[int] + UPTIME_SECONDS_FIELD_NUMBER: _ClassVar[int] + ACTIVE_MEETINGS_FIELD_NUMBER: _ClassVar[int] + DIARIZATION_ENABLED_FIELD_NUMBER: _ClassVar[int] + DIARIZATION_READY_FIELD_NUMBER: _ClassVar[int] + STATE_VERSION_FIELD_NUMBER: _ClassVar[int] + SYSTEM_RAM_TOTAL_BYTES_FIELD_NUMBER: _ClassVar[int] + SYSTEM_RAM_AVAILABLE_BYTES_FIELD_NUMBER: _ClassVar[int] + GPU_VRAM_TOTAL_BYTES_FIELD_NUMBER: _ClassVar[int] + GPU_VRAM_AVAILABLE_BYTES_FIELD_NUMBER: _ClassVar[int] + version: str + asr_model: str + asr_ready: bool + supported_sample_rates: _containers.RepeatedScalarFieldContainer[int] + max_chunk_size: int + uptime_seconds: float + active_meetings: int + diarization_enabled: bool + diarization_ready: bool + state_version: int + system_ram_total_bytes: int + system_ram_available_bytes: int + gpu_vram_total_bytes: int + gpu_vram_available_bytes: int + def __init__(self, version: _Optional[str] = ..., asr_model: _Optional[str] = ..., asr_ready: bool = ..., supported_sample_rates: _Optional[_Iterable[int]] = ..., max_chunk_size: _Optional[int] = ..., uptime_seconds: _Optional[float] = ..., active_meetings: _Optional[int] = ..., diarization_enabled: bool = ..., diarization_ready: bool = ..., state_version: _Optional[int] = ..., system_ram_total_bytes: _Optional[int] = ..., system_ram_available_bytes: _Optional[int] = ..., gpu_vram_total_bytes: _Optional[int] = ..., gpu_vram_available_bytes: _Optional[int] = ...) -> None: ... + +class AsrConfiguration(_message.Message): + __slots__ = ("model_size", "device", "compute_type", "is_ready", "cuda_available", "available_model_sizes", "available_compute_types", "rocm_available", "gpu_backend") + MODEL_SIZE_FIELD_NUMBER: _ClassVar[int] + DEVICE_FIELD_NUMBER: _ClassVar[int] + COMPUTE_TYPE_FIELD_NUMBER: _ClassVar[int] + IS_READY_FIELD_NUMBER: _ClassVar[int] + CUDA_AVAILABLE_FIELD_NUMBER: _ClassVar[int] + AVAILABLE_MODEL_SIZES_FIELD_NUMBER: _ClassVar[int] + AVAILABLE_COMPUTE_TYPES_FIELD_NUMBER: _ClassVar[int] + ROCM_AVAILABLE_FIELD_NUMBER: _ClassVar[int] + GPU_BACKEND_FIELD_NUMBER: _ClassVar[int] + model_size: str + device: AsrDevice + compute_type: AsrComputeType + is_ready: bool + cuda_available: bool + available_model_sizes: _containers.RepeatedScalarFieldContainer[str] + available_compute_types: _containers.RepeatedScalarFieldContainer[AsrComputeType] + rocm_available: bool + gpu_backend: str + def __init__(self, model_size: _Optional[str] = ..., device: _Optional[_Union[AsrDevice, str]] = ..., compute_type: _Optional[_Union[AsrComputeType, str]] = ..., is_ready: bool = ..., cuda_available: bool = ..., available_model_sizes: _Optional[_Iterable[str]] = ..., available_compute_types: _Optional[_Iterable[_Union[AsrComputeType, str]]] = ..., rocm_available: bool = ..., gpu_backend: _Optional[str] = ...) -> None: ... + +class GetAsrConfigurationRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetAsrConfigurationResponse(_message.Message): + __slots__ = ("configuration",) + CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + configuration: AsrConfiguration + def __init__(self, configuration: _Optional[_Union[AsrConfiguration, _Mapping]] = ...) -> None: ... + +class UpdateAsrConfigurationRequest(_message.Message): + __slots__ = ("model_size", "device", "compute_type") + MODEL_SIZE_FIELD_NUMBER: _ClassVar[int] + DEVICE_FIELD_NUMBER: _ClassVar[int] + COMPUTE_TYPE_FIELD_NUMBER: _ClassVar[int] + model_size: str + device: AsrDevice + compute_type: AsrComputeType + def __init__(self, model_size: _Optional[str] = ..., device: _Optional[_Union[AsrDevice, str]] = ..., compute_type: _Optional[_Union[AsrComputeType, str]] = ...) -> None: ... + +class UpdateAsrConfigurationResponse(_message.Message): + __slots__ = ("job_id", "status", "error_message", "accepted") + JOB_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + ACCEPTED_FIELD_NUMBER: _ClassVar[int] + job_id: str + status: JobStatus + error_message: str + accepted: bool + def __init__(self, job_id: _Optional[str] = ..., status: _Optional[_Union[JobStatus, str]] = ..., error_message: _Optional[str] = ..., accepted: bool = ...) -> None: ... + +class GetAsrConfigurationJobStatusRequest(_message.Message): + __slots__ = ("job_id",) + JOB_ID_FIELD_NUMBER: _ClassVar[int] + job_id: str + def __init__(self, job_id: _Optional[str] = ...) -> None: ... + +class AsrConfigurationJobStatus(_message.Message): + __slots__ = ("job_id", "status", "progress_percent", "phase", "error_message", "new_configuration") + JOB_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + PROGRESS_PERCENT_FIELD_NUMBER: _ClassVar[int] + PHASE_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + NEW_CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + job_id: str + status: JobStatus + progress_percent: float + phase: str + error_message: str + new_configuration: AsrConfiguration + def __init__(self, job_id: _Optional[str] = ..., status: _Optional[_Union[JobStatus, str]] = ..., progress_percent: _Optional[float] = ..., phase: _Optional[str] = ..., error_message: _Optional[str] = ..., new_configuration: _Optional[_Union[AsrConfiguration, _Mapping]] = ...) -> None: ... + +class StreamingConfiguration(_message.Message): + __slots__ = ("partial_cadence_seconds", "min_partial_audio_seconds", "max_segment_duration_seconds", "min_speech_duration_seconds", "trailing_silence_seconds", "leading_buffer_seconds") + PARTIAL_CADENCE_SECONDS_FIELD_NUMBER: _ClassVar[int] + MIN_PARTIAL_AUDIO_SECONDS_FIELD_NUMBER: _ClassVar[int] + MAX_SEGMENT_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int] + MIN_SPEECH_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int] + TRAILING_SILENCE_SECONDS_FIELD_NUMBER: _ClassVar[int] + LEADING_BUFFER_SECONDS_FIELD_NUMBER: _ClassVar[int] + partial_cadence_seconds: float + min_partial_audio_seconds: float + max_segment_duration_seconds: float + min_speech_duration_seconds: float + trailing_silence_seconds: float + leading_buffer_seconds: float + def __init__(self, partial_cadence_seconds: _Optional[float] = ..., min_partial_audio_seconds: _Optional[float] = ..., max_segment_duration_seconds: _Optional[float] = ..., min_speech_duration_seconds: _Optional[float] = ..., trailing_silence_seconds: _Optional[float] = ..., leading_buffer_seconds: _Optional[float] = ...) -> None: ... + +class GetStreamingConfigurationRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetStreamingConfigurationResponse(_message.Message): + __slots__ = ("configuration",) + CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + configuration: StreamingConfiguration + def __init__(self, configuration: _Optional[_Union[StreamingConfiguration, _Mapping]] = ...) -> None: ... + +class UpdateStreamingConfigurationRequest(_message.Message): + __slots__ = ("partial_cadence_seconds", "min_partial_audio_seconds", "max_segment_duration_seconds", "min_speech_duration_seconds", "trailing_silence_seconds", "leading_buffer_seconds") + PARTIAL_CADENCE_SECONDS_FIELD_NUMBER: _ClassVar[int] + MIN_PARTIAL_AUDIO_SECONDS_FIELD_NUMBER: _ClassVar[int] + MAX_SEGMENT_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int] + MIN_SPEECH_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int] + TRAILING_SILENCE_SECONDS_FIELD_NUMBER: _ClassVar[int] + LEADING_BUFFER_SECONDS_FIELD_NUMBER: _ClassVar[int] + partial_cadence_seconds: float + min_partial_audio_seconds: float + max_segment_duration_seconds: float + min_speech_duration_seconds: float + trailing_silence_seconds: float + leading_buffer_seconds: float + def __init__(self, partial_cadence_seconds: _Optional[float] = ..., min_partial_audio_seconds: _Optional[float] = ..., max_segment_duration_seconds: _Optional[float] = ..., min_speech_duration_seconds: _Optional[float] = ..., trailing_silence_seconds: _Optional[float] = ..., leading_buffer_seconds: _Optional[float] = ...) -> None: ... + +class UpdateStreamingConfigurationResponse(_message.Message): + __slots__ = ("configuration",) + CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + configuration: StreamingConfiguration + def __init__(self, configuration: _Optional[_Union[StreamingConfiguration, _Mapping]] = ...) -> None: ... + +class Annotation(_message.Message): + __slots__ = ("id", "meeting_id", "annotation_type", "text", "start_time", "end_time", "segment_ids", "created_at") + ID_FIELD_NUMBER: _ClassVar[int] + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + ANNOTATION_TYPE_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + meeting_id: str + annotation_type: AnnotationType + text: str + start_time: float + end_time: float + segment_ids: _containers.RepeatedScalarFieldContainer[int] + created_at: float + def __init__(self, id: _Optional[str] = ..., meeting_id: _Optional[str] = ..., annotation_type: _Optional[_Union[AnnotationType, str]] = ..., text: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., segment_ids: _Optional[_Iterable[int]] = ..., created_at: _Optional[float] = ...) -> None: ... + +class AddAnnotationRequest(_message.Message): + __slots__ = ("meeting_id", "annotation_type", "text", "start_time", "end_time", "segment_ids") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + ANNOTATION_TYPE_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + annotation_type: AnnotationType + text: str + start_time: float + end_time: float + segment_ids: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, meeting_id: _Optional[str] = ..., annotation_type: _Optional[_Union[AnnotationType, str]] = ..., text: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., segment_ids: _Optional[_Iterable[int]] = ...) -> None: ... + +class GetAnnotationRequest(_message.Message): + __slots__ = ("annotation_id",) + ANNOTATION_ID_FIELD_NUMBER: _ClassVar[int] + annotation_id: str + def __init__(self, annotation_id: _Optional[str] = ...) -> None: ... + +class ListAnnotationsRequest(_message.Message): + __slots__ = ("meeting_id", "start_time", "end_time") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + start_time: float + end_time: float + def __init__(self, meeting_id: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ...) -> None: ... + +class ListAnnotationsResponse(_message.Message): + __slots__ = ("annotations",) + ANNOTATIONS_FIELD_NUMBER: _ClassVar[int] + annotations: _containers.RepeatedCompositeFieldContainer[Annotation] + def __init__(self, annotations: _Optional[_Iterable[_Union[Annotation, _Mapping]]] = ...) -> None: ... + +class UpdateAnnotationRequest(_message.Message): + __slots__ = ("annotation_id", "annotation_type", "text", "start_time", "end_time", "segment_ids") + ANNOTATION_ID_FIELD_NUMBER: _ClassVar[int] + ANNOTATION_TYPE_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + annotation_id: str + annotation_type: AnnotationType + text: str + start_time: float + end_time: float + segment_ids: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, annotation_id: _Optional[str] = ..., annotation_type: _Optional[_Union[AnnotationType, str]] = ..., text: _Optional[str] = ..., start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., segment_ids: _Optional[_Iterable[int]] = ...) -> None: ... + +class DeleteAnnotationRequest(_message.Message): + __slots__ = ("annotation_id",) + ANNOTATION_ID_FIELD_NUMBER: _ClassVar[int] + annotation_id: str + def __init__(self, annotation_id: _Optional[str] = ...) -> None: ... + +class DeleteAnnotationResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class ProcessingStepState(_message.Message): + __slots__ = ("status", "error_message", "started_at", "completed_at") + STATUS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + STARTED_AT_FIELD_NUMBER: _ClassVar[int] + COMPLETED_AT_FIELD_NUMBER: _ClassVar[int] + status: ProcessingStepStatus + error_message: str + started_at: float + completed_at: float + def __init__(self, status: _Optional[_Union[ProcessingStepStatus, str]] = ..., error_message: _Optional[str] = ..., started_at: _Optional[float] = ..., completed_at: _Optional[float] = ...) -> None: ... + +class ProcessingStatus(_message.Message): + __slots__ = ("summary", "entities", "diarization") + SUMMARY_FIELD_NUMBER: _ClassVar[int] + ENTITIES_FIELD_NUMBER: _ClassVar[int] + DIARIZATION_FIELD_NUMBER: _ClassVar[int] + summary: ProcessingStepState + entities: ProcessingStepState + diarization: ProcessingStepState + def __init__(self, summary: _Optional[_Union[ProcessingStepState, _Mapping]] = ..., entities: _Optional[_Union[ProcessingStepState, _Mapping]] = ..., diarization: _Optional[_Union[ProcessingStepState, _Mapping]] = ...) -> None: ... + +class ExportTranscriptRequest(_message.Message): + __slots__ = ("meeting_id", "format") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + format: ExportFormat + def __init__(self, meeting_id: _Optional[str] = ..., format: _Optional[_Union[ExportFormat, str]] = ...) -> None: ... + +class ExportTranscriptResponse(_message.Message): + __slots__ = ("content", "format_name", "file_extension") + CONTENT_FIELD_NUMBER: _ClassVar[int] + FORMAT_NAME_FIELD_NUMBER: _ClassVar[int] + FILE_EXTENSION_FIELD_NUMBER: _ClassVar[int] + content: str + format_name: str + file_extension: str + def __init__(self, content: _Optional[str] = ..., format_name: _Optional[str] = ..., file_extension: _Optional[str] = ...) -> None: ... + +class RefineSpeakerDiarizationRequest(_message.Message): + __slots__ = ("meeting_id", "num_speakers") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + NUM_SPEAKERS_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + num_speakers: int + def __init__(self, meeting_id: _Optional[str] = ..., num_speakers: _Optional[int] = ...) -> None: ... + +class RefineSpeakerDiarizationResponse(_message.Message): + __slots__ = ("segments_updated", "speaker_ids", "error_message", "job_id", "status") + SEGMENTS_UPDATED_FIELD_NUMBER: _ClassVar[int] + SPEAKER_IDS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + JOB_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + segments_updated: int + speaker_ids: _containers.RepeatedScalarFieldContainer[str] + error_message: str + job_id: str + status: JobStatus + def __init__(self, segments_updated: _Optional[int] = ..., speaker_ids: _Optional[_Iterable[str]] = ..., error_message: _Optional[str] = ..., job_id: _Optional[str] = ..., status: _Optional[_Union[JobStatus, str]] = ...) -> None: ... + +class RenameSpeakerRequest(_message.Message): + __slots__ = ("meeting_id", "old_speaker_id", "new_speaker_name") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + OLD_SPEAKER_ID_FIELD_NUMBER: _ClassVar[int] + NEW_SPEAKER_NAME_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + old_speaker_id: str + new_speaker_name: str + def __init__(self, meeting_id: _Optional[str] = ..., old_speaker_id: _Optional[str] = ..., new_speaker_name: _Optional[str] = ...) -> None: ... + +class RenameSpeakerResponse(_message.Message): + __slots__ = ("segments_updated", "success") + SEGMENTS_UPDATED_FIELD_NUMBER: _ClassVar[int] + SUCCESS_FIELD_NUMBER: _ClassVar[int] + segments_updated: int + success: bool + def __init__(self, segments_updated: _Optional[int] = ..., success: bool = ...) -> None: ... + +class GetDiarizationJobStatusRequest(_message.Message): + __slots__ = ("job_id",) + JOB_ID_FIELD_NUMBER: _ClassVar[int] + job_id: str + def __init__(self, job_id: _Optional[str] = ...) -> None: ... + +class DiarizationJobStatus(_message.Message): + __slots__ = ("job_id", "status", "segments_updated", "speaker_ids", "error_message", "progress_percent") + JOB_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + SEGMENTS_UPDATED_FIELD_NUMBER: _ClassVar[int] + SPEAKER_IDS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + PROGRESS_PERCENT_FIELD_NUMBER: _ClassVar[int] + job_id: str + status: JobStatus + segments_updated: int + speaker_ids: _containers.RepeatedScalarFieldContainer[str] + error_message: str + progress_percent: float + def __init__(self, job_id: _Optional[str] = ..., status: _Optional[_Union[JobStatus, str]] = ..., segments_updated: _Optional[int] = ..., speaker_ids: _Optional[_Iterable[str]] = ..., error_message: _Optional[str] = ..., progress_percent: _Optional[float] = ...) -> None: ... + +class CancelDiarizationJobRequest(_message.Message): + __slots__ = ("job_id",) + JOB_ID_FIELD_NUMBER: _ClassVar[int] + job_id: str + def __init__(self, job_id: _Optional[str] = ...) -> None: ... + +class CancelDiarizationJobResponse(_message.Message): + __slots__ = ("success", "error_message", "status") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + success: bool + error_message: str + status: JobStatus + def __init__(self, success: bool = ..., error_message: _Optional[str] = ..., status: _Optional[_Union[JobStatus, str]] = ...) -> None: ... + +class GetActiveDiarizationJobsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetActiveDiarizationJobsResponse(_message.Message): + __slots__ = ("jobs",) + JOBS_FIELD_NUMBER: _ClassVar[int] + jobs: _containers.RepeatedCompositeFieldContainer[DiarizationJobStatus] + def __init__(self, jobs: _Optional[_Iterable[_Union[DiarizationJobStatus, _Mapping]]] = ...) -> None: ... + +class ExtractEntitiesRequest(_message.Message): + __slots__ = ("meeting_id", "force_refresh") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + FORCE_REFRESH_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + force_refresh: bool + def __init__(self, meeting_id: _Optional[str] = ..., force_refresh: bool = ...) -> None: ... + +class ExtractedEntity(_message.Message): + __slots__ = ("id", "text", "category", "segment_ids", "confidence", "is_pinned") + ID_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + CATEGORY_FIELD_NUMBER: _ClassVar[int] + SEGMENT_IDS_FIELD_NUMBER: _ClassVar[int] + CONFIDENCE_FIELD_NUMBER: _ClassVar[int] + IS_PINNED_FIELD_NUMBER: _ClassVar[int] + id: str + text: str + category: str + segment_ids: _containers.RepeatedScalarFieldContainer[int] + confidence: float + is_pinned: bool + def __init__(self, id: _Optional[str] = ..., text: _Optional[str] = ..., category: _Optional[str] = ..., segment_ids: _Optional[_Iterable[int]] = ..., confidence: _Optional[float] = ..., is_pinned: bool = ...) -> None: ... + +class ExtractEntitiesResponse(_message.Message): + __slots__ = ("entities", "total_count", "cached") + ENTITIES_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + CACHED_FIELD_NUMBER: _ClassVar[int] + entities: _containers.RepeatedCompositeFieldContainer[ExtractedEntity] + total_count: int + cached: bool + def __init__(self, entities: _Optional[_Iterable[_Union[ExtractedEntity, _Mapping]]] = ..., total_count: _Optional[int] = ..., cached: bool = ...) -> None: ... + +class UpdateEntityRequest(_message.Message): + __slots__ = ("meeting_id", "entity_id", "text", "category") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + ENTITY_ID_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + CATEGORY_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + entity_id: str + text: str + category: str + def __init__(self, meeting_id: _Optional[str] = ..., entity_id: _Optional[str] = ..., text: _Optional[str] = ..., category: _Optional[str] = ...) -> None: ... + +class UpdateEntityResponse(_message.Message): + __slots__ = ("entity",) + ENTITY_FIELD_NUMBER: _ClassVar[int] + entity: ExtractedEntity + def __init__(self, entity: _Optional[_Union[ExtractedEntity, _Mapping]] = ...) -> None: ... + +class DeleteEntityRequest(_message.Message): + __slots__ = ("meeting_id", "entity_id") + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + ENTITY_ID_FIELD_NUMBER: _ClassVar[int] + meeting_id: str + entity_id: str + def __init__(self, meeting_id: _Optional[str] = ..., entity_id: _Optional[str] = ...) -> None: ... + +class DeleteEntityResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class CalendarEvent(_message.Message): + __slots__ = ("id", "title", "start_time", "end_time", "attendees", "location", "description", "meeting_url", "is_recurring", "provider") + ID_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + ATTENDEES_FIELD_NUMBER: _ClassVar[int] + LOCATION_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + MEETING_URL_FIELD_NUMBER: _ClassVar[int] + IS_RECURRING_FIELD_NUMBER: _ClassVar[int] + PROVIDER_FIELD_NUMBER: _ClassVar[int] + id: str + title: str + start_time: int + end_time: int + attendees: _containers.RepeatedScalarFieldContainer[str] + location: str + description: str + meeting_url: str + is_recurring: bool + provider: str + def __init__(self, id: _Optional[str] = ..., title: _Optional[str] = ..., start_time: _Optional[int] = ..., end_time: _Optional[int] = ..., attendees: _Optional[_Iterable[str]] = ..., location: _Optional[str] = ..., description: _Optional[str] = ..., meeting_url: _Optional[str] = ..., is_recurring: bool = ..., provider: _Optional[str] = ...) -> None: ... + +class ListCalendarEventsRequest(_message.Message): + __slots__ = ("hours_ahead", "limit", "provider") + HOURS_AHEAD_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + PROVIDER_FIELD_NUMBER: _ClassVar[int] + hours_ahead: int + limit: int + provider: str + def __init__(self, hours_ahead: _Optional[int] = ..., limit: _Optional[int] = ..., provider: _Optional[str] = ...) -> None: ... + +class ListCalendarEventsResponse(_message.Message): + __slots__ = ("events", "total_count") + EVENTS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + events: _containers.RepeatedCompositeFieldContainer[CalendarEvent] + total_count: int + def __init__(self, events: _Optional[_Iterable[_Union[CalendarEvent, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GetCalendarProvidersRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class CalendarProvider(_message.Message): + __slots__ = ("name", "is_authenticated", "display_name") + NAME_FIELD_NUMBER: _ClassVar[int] + IS_AUTHENTICATED_FIELD_NUMBER: _ClassVar[int] + DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int] + name: str + is_authenticated: bool + display_name: str + def __init__(self, name: _Optional[str] = ..., is_authenticated: bool = ..., display_name: _Optional[str] = ...) -> None: ... + +class GetCalendarProvidersResponse(_message.Message): + __slots__ = ("providers",) + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[CalendarProvider] + def __init__(self, providers: _Optional[_Iterable[_Union[CalendarProvider, _Mapping]]] = ...) -> None: ... + +class InitiateOAuthRequest(_message.Message): + __slots__ = ("provider", "redirect_uri", "integration_type") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + REDIRECT_URI_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + provider: str + redirect_uri: str + integration_type: str + def __init__(self, provider: _Optional[str] = ..., redirect_uri: _Optional[str] = ..., integration_type: _Optional[str] = ...) -> None: ... + +class InitiateOAuthResponse(_message.Message): + __slots__ = ("auth_url", "state") + AUTH_URL_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + auth_url: str + state: str + def __init__(self, auth_url: _Optional[str] = ..., state: _Optional[str] = ...) -> None: ... + +class CompleteOAuthRequest(_message.Message): + __slots__ = ("provider", "code", "state") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + CODE_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + provider: str + code: str + state: str + def __init__(self, provider: _Optional[str] = ..., code: _Optional[str] = ..., state: _Optional[str] = ...) -> None: ... + +class CompleteOAuthResponse(_message.Message): + __slots__ = ("success", "error_message", "provider_email", "integration_id") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + PROVIDER_EMAIL_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_ID_FIELD_NUMBER: _ClassVar[int] + success: bool + error_message: str + provider_email: str + integration_id: str + def __init__(self, success: bool = ..., error_message: _Optional[str] = ..., provider_email: _Optional[str] = ..., integration_id: _Optional[str] = ...) -> None: ... + +class OAuthConnection(_message.Message): + __slots__ = ("provider", "status", "email", "expires_at", "error_message", "integration_type") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + EMAIL_FIELD_NUMBER: _ClassVar[int] + EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + provider: str + status: str + email: str + expires_at: int + error_message: str + integration_type: str + def __init__(self, provider: _Optional[str] = ..., status: _Optional[str] = ..., email: _Optional[str] = ..., expires_at: _Optional[int] = ..., error_message: _Optional[str] = ..., integration_type: _Optional[str] = ...) -> None: ... + +class GetOAuthConnectionStatusRequest(_message.Message): + __slots__ = ("provider", "integration_type") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + provider: str + integration_type: str + def __init__(self, provider: _Optional[str] = ..., integration_type: _Optional[str] = ...) -> None: ... + +class GetOAuthConnectionStatusResponse(_message.Message): + __slots__ = ("connection",) + CONNECTION_FIELD_NUMBER: _ClassVar[int] + connection: OAuthConnection + def __init__(self, connection: _Optional[_Union[OAuthConnection, _Mapping]] = ...) -> None: ... + +class DisconnectOAuthRequest(_message.Message): + __slots__ = ("provider", "integration_type") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + provider: str + integration_type: str + def __init__(self, provider: _Optional[str] = ..., integration_type: _Optional[str] = ...) -> None: ... + +class DisconnectOAuthResponse(_message.Message): + __slots__ = ("success", "error_message") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + success: bool + error_message: str + def __init__(self, success: bool = ..., error_message: _Optional[str] = ...) -> None: ... + +class OAuthClientConfig(_message.Message): + __slots__ = ("client_id", "client_secret", "redirect_uri", "scopes", "override_enabled", "has_client_secret") + CLIENT_ID_FIELD_NUMBER: _ClassVar[int] + CLIENT_SECRET_FIELD_NUMBER: _ClassVar[int] + REDIRECT_URI_FIELD_NUMBER: _ClassVar[int] + SCOPES_FIELD_NUMBER: _ClassVar[int] + OVERRIDE_ENABLED_FIELD_NUMBER: _ClassVar[int] + HAS_CLIENT_SECRET_FIELD_NUMBER: _ClassVar[int] + client_id: str + client_secret: str + redirect_uri: str + scopes: _containers.RepeatedScalarFieldContainer[str] + override_enabled: bool + has_client_secret: bool + def __init__(self, client_id: _Optional[str] = ..., client_secret: _Optional[str] = ..., redirect_uri: _Optional[str] = ..., scopes: _Optional[_Iterable[str]] = ..., override_enabled: bool = ..., has_client_secret: bool = ...) -> None: ... + +class GetOAuthClientConfigRequest(_message.Message): + __slots__ = ("provider", "integration_type", "workspace_id") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + provider: str + integration_type: str + workspace_id: str + def __init__(self, provider: _Optional[str] = ..., integration_type: _Optional[str] = ..., workspace_id: _Optional[str] = ...) -> None: ... + +class GetOAuthClientConfigResponse(_message.Message): + __slots__ = ("config",) + CONFIG_FIELD_NUMBER: _ClassVar[int] + config: OAuthClientConfig + def __init__(self, config: _Optional[_Union[OAuthClientConfig, _Mapping]] = ...) -> None: ... + +class SetOAuthClientConfigRequest(_message.Message): + __slots__ = ("provider", "integration_type", "workspace_id", "config") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_TYPE_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + provider: str + integration_type: str + workspace_id: str + config: OAuthClientConfig + def __init__(self, provider: _Optional[str] = ..., integration_type: _Optional[str] = ..., workspace_id: _Optional[str] = ..., config: _Optional[_Union[OAuthClientConfig, _Mapping]] = ...) -> None: ... + +class SetOAuthClientConfigResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class RegisterWebhookRequest(_message.Message): + __slots__ = ("workspace_id", "url", "events", "name", "secret", "timeout_ms", "max_retries") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + EVENTS_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SECRET_FIELD_NUMBER: _ClassVar[int] + TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + MAX_RETRIES_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + url: str + events: _containers.RepeatedScalarFieldContainer[str] + name: str + secret: str + timeout_ms: int + max_retries: int + def __init__(self, workspace_id: _Optional[str] = ..., url: _Optional[str] = ..., events: _Optional[_Iterable[str]] = ..., name: _Optional[str] = ..., secret: _Optional[str] = ..., timeout_ms: _Optional[int] = ..., max_retries: _Optional[int] = ...) -> None: ... + +class WebhookConfigProto(_message.Message): + __slots__ = ("id", "workspace_id", "name", "url", "events", "enabled", "timeout_ms", "max_retries", "created_at", "updated_at") + ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + EVENTS_FIELD_NUMBER: _ClassVar[int] + ENABLED_FIELD_NUMBER: _ClassVar[int] + TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + MAX_RETRIES_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + workspace_id: str + name: str + url: str + events: _containers.RepeatedScalarFieldContainer[str] + enabled: bool + timeout_ms: int + max_retries: int + created_at: int + updated_at: int + def __init__(self, id: _Optional[str] = ..., workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., url: _Optional[str] = ..., events: _Optional[_Iterable[str]] = ..., enabled: bool = ..., timeout_ms: _Optional[int] = ..., max_retries: _Optional[int] = ..., created_at: _Optional[int] = ..., updated_at: _Optional[int] = ...) -> None: ... + +class ListWebhooksRequest(_message.Message): + __slots__ = ("enabled_only",) + ENABLED_ONLY_FIELD_NUMBER: _ClassVar[int] + enabled_only: bool + def __init__(self, enabled_only: bool = ...) -> None: ... + +class ListWebhooksResponse(_message.Message): + __slots__ = ("webhooks", "total_count") + WEBHOOKS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + webhooks: _containers.RepeatedCompositeFieldContainer[WebhookConfigProto] + total_count: int + def __init__(self, webhooks: _Optional[_Iterable[_Union[WebhookConfigProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class UpdateWebhookRequest(_message.Message): + __slots__ = ("webhook_id", "url", "events", "name", "secret", "enabled", "timeout_ms", "max_retries") + WEBHOOK_ID_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + EVENTS_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SECRET_FIELD_NUMBER: _ClassVar[int] + ENABLED_FIELD_NUMBER: _ClassVar[int] + TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int] + MAX_RETRIES_FIELD_NUMBER: _ClassVar[int] + webhook_id: str + url: str + events: _containers.RepeatedScalarFieldContainer[str] + name: str + secret: str + enabled: bool + timeout_ms: int + max_retries: int + def __init__(self, webhook_id: _Optional[str] = ..., url: _Optional[str] = ..., events: _Optional[_Iterable[str]] = ..., name: _Optional[str] = ..., secret: _Optional[str] = ..., enabled: bool = ..., timeout_ms: _Optional[int] = ..., max_retries: _Optional[int] = ...) -> None: ... + +class DeleteWebhookRequest(_message.Message): + __slots__ = ("webhook_id",) + WEBHOOK_ID_FIELD_NUMBER: _ClassVar[int] + webhook_id: str + def __init__(self, webhook_id: _Optional[str] = ...) -> None: ... + +class DeleteWebhookResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class WebhookDeliveryProto(_message.Message): + __slots__ = ("id", "webhook_id", "event_type", "status_code", "error_message", "attempt_count", "duration_ms", "delivered_at", "succeeded") + ID_FIELD_NUMBER: _ClassVar[int] + WEBHOOK_ID_FIELD_NUMBER: _ClassVar[int] + EVENT_TYPE_FIELD_NUMBER: _ClassVar[int] + STATUS_CODE_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + ATTEMPT_COUNT_FIELD_NUMBER: _ClassVar[int] + DURATION_MS_FIELD_NUMBER: _ClassVar[int] + DELIVERED_AT_FIELD_NUMBER: _ClassVar[int] + SUCCEEDED_FIELD_NUMBER: _ClassVar[int] + id: str + webhook_id: str + event_type: str + status_code: int + error_message: str + attempt_count: int + duration_ms: int + delivered_at: int + succeeded: bool + def __init__(self, id: _Optional[str] = ..., webhook_id: _Optional[str] = ..., event_type: _Optional[str] = ..., status_code: _Optional[int] = ..., error_message: _Optional[str] = ..., attempt_count: _Optional[int] = ..., duration_ms: _Optional[int] = ..., delivered_at: _Optional[int] = ..., succeeded: bool = ...) -> None: ... + +class GetWebhookDeliveriesRequest(_message.Message): + __slots__ = ("webhook_id", "limit") + WEBHOOK_ID_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + webhook_id: str + limit: int + def __init__(self, webhook_id: _Optional[str] = ..., limit: _Optional[int] = ...) -> None: ... + +class GetWebhookDeliveriesResponse(_message.Message): + __slots__ = ("deliveries", "total_count") + DELIVERIES_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + deliveries: _containers.RepeatedCompositeFieldContainer[WebhookDeliveryProto] + total_count: int + def __init__(self, deliveries: _Optional[_Iterable[_Union[WebhookDeliveryProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GrantCloudConsentRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GrantCloudConsentResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class RevokeCloudConsentRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class RevokeCloudConsentResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCloudConsentStatusRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCloudConsentStatusResponse(_message.Message): + __slots__ = ("consent_granted",) + CONSENT_GRANTED_FIELD_NUMBER: _ClassVar[int] + consent_granted: bool + def __init__(self, consent_granted: bool = ...) -> None: ... + +class SetHuggingFaceTokenRequest(_message.Message): + __slots__ = ("token", "validate") + TOKEN_FIELD_NUMBER: _ClassVar[int] + VALIDATE_FIELD_NUMBER: _ClassVar[int] + token: str + validate: bool + def __init__(self, token: _Optional[str] = ..., validate: bool = ...) -> None: ... + +class SetHuggingFaceTokenResponse(_message.Message): + __slots__ = ("success", "valid", "validation_error", "username") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + VALID_FIELD_NUMBER: _ClassVar[int] + VALIDATION_ERROR_FIELD_NUMBER: _ClassVar[int] + USERNAME_FIELD_NUMBER: _ClassVar[int] + success: bool + valid: bool + validation_error: str + username: str + def __init__(self, success: bool = ..., valid: bool = ..., validation_error: _Optional[str] = ..., username: _Optional[str] = ...) -> None: ... + +class GetHuggingFaceTokenStatusRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetHuggingFaceTokenStatusResponse(_message.Message): + __slots__ = ("is_configured", "is_validated", "username", "validated_at") + IS_CONFIGURED_FIELD_NUMBER: _ClassVar[int] + IS_VALIDATED_FIELD_NUMBER: _ClassVar[int] + USERNAME_FIELD_NUMBER: _ClassVar[int] + VALIDATED_AT_FIELD_NUMBER: _ClassVar[int] + is_configured: bool + is_validated: bool + username: str + validated_at: float + def __init__(self, is_configured: bool = ..., is_validated: bool = ..., username: _Optional[str] = ..., validated_at: _Optional[float] = ...) -> None: ... + +class DeleteHuggingFaceTokenRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DeleteHuggingFaceTokenResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class ValidateHuggingFaceTokenRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ValidateHuggingFaceTokenResponse(_message.Message): + __slots__ = ("valid", "username", "error_message") + VALID_FIELD_NUMBER: _ClassVar[int] + USERNAME_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + valid: bool + username: str + error_message: str + def __init__(self, valid: bool = ..., username: _Optional[str] = ..., error_message: _Optional[str] = ...) -> None: ... + +class GetPreferencesRequest(_message.Message): + __slots__ = ("keys",) + KEYS_FIELD_NUMBER: _ClassVar[int] + keys: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, keys: _Optional[_Iterable[str]] = ...) -> None: ... + +class GetPreferencesResponse(_message.Message): + __slots__ = ("preferences", "updated_at", "etag") + class PreferencesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + PREFERENCES_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + ETAG_FIELD_NUMBER: _ClassVar[int] + preferences: _containers.ScalarMap[str, str] + updated_at: float + etag: str + def __init__(self, preferences: _Optional[_Mapping[str, str]] = ..., updated_at: _Optional[float] = ..., etag: _Optional[str] = ...) -> None: ... + +class SetPreferencesRequest(_message.Message): + __slots__ = ("preferences", "if_match", "client_updated_at", "merge") + class PreferencesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + PREFERENCES_FIELD_NUMBER: _ClassVar[int] + IF_MATCH_FIELD_NUMBER: _ClassVar[int] + CLIENT_UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + MERGE_FIELD_NUMBER: _ClassVar[int] + preferences: _containers.ScalarMap[str, str] + if_match: str + client_updated_at: float + merge: bool + def __init__(self, preferences: _Optional[_Mapping[str, str]] = ..., if_match: _Optional[str] = ..., client_updated_at: _Optional[float] = ..., merge: bool = ...) -> None: ... + +class SetPreferencesResponse(_message.Message): + __slots__ = ("success", "conflict", "server_preferences", "server_updated_at", "etag", "conflict_message") + class ServerPreferencesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + SUCCESS_FIELD_NUMBER: _ClassVar[int] + CONFLICT_FIELD_NUMBER: _ClassVar[int] + SERVER_PREFERENCES_FIELD_NUMBER: _ClassVar[int] + SERVER_UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + ETAG_FIELD_NUMBER: _ClassVar[int] + CONFLICT_MESSAGE_FIELD_NUMBER: _ClassVar[int] + success: bool + conflict: bool + server_preferences: _containers.ScalarMap[str, str] + server_updated_at: float + etag: str + conflict_message: str + def __init__(self, success: bool = ..., conflict: bool = ..., server_preferences: _Optional[_Mapping[str, str]] = ..., server_updated_at: _Optional[float] = ..., etag: _Optional[str] = ..., conflict_message: _Optional[str] = ...) -> None: ... + +class StartIntegrationSyncRequest(_message.Message): + __slots__ = ("integration_id",) + INTEGRATION_ID_FIELD_NUMBER: _ClassVar[int] + integration_id: str + def __init__(self, integration_id: _Optional[str] = ...) -> None: ... + +class StartIntegrationSyncResponse(_message.Message): + __slots__ = ("sync_run_id", "status") + SYNC_RUN_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + sync_run_id: str + status: str + def __init__(self, sync_run_id: _Optional[str] = ..., status: _Optional[str] = ...) -> None: ... + +class GetSyncStatusRequest(_message.Message): + __slots__ = ("sync_run_id",) + SYNC_RUN_ID_FIELD_NUMBER: _ClassVar[int] + sync_run_id: str + def __init__(self, sync_run_id: _Optional[str] = ...) -> None: ... + +class GetSyncStatusResponse(_message.Message): + __slots__ = ("status", "items_synced", "items_total", "duration_ms", "error_code", "expires_at", "not_found_reason") + STATUS_FIELD_NUMBER: _ClassVar[int] + ITEMS_SYNCED_FIELD_NUMBER: _ClassVar[int] + ITEMS_TOTAL_FIELD_NUMBER: _ClassVar[int] + DURATION_MS_FIELD_NUMBER: _ClassVar[int] + ERROR_CODE_FIELD_NUMBER: _ClassVar[int] + EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + NOT_FOUND_REASON_FIELD_NUMBER: _ClassVar[int] + status: str + items_synced: int + items_total: int + duration_ms: int + error_code: SyncErrorCode + expires_at: str + not_found_reason: str + def __init__(self, status: _Optional[str] = ..., items_synced: _Optional[int] = ..., items_total: _Optional[int] = ..., duration_ms: _Optional[int] = ..., error_code: _Optional[_Union[SyncErrorCode, str]] = ..., expires_at: _Optional[str] = ..., not_found_reason: _Optional[str] = ...) -> None: ... + +class ListSyncHistoryRequest(_message.Message): + __slots__ = ("integration_id", "limit", "offset") + INTEGRATION_ID_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + integration_id: str + limit: int + offset: int + def __init__(self, integration_id: _Optional[str] = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListSyncHistoryResponse(_message.Message): + __slots__ = ("runs", "total_count") + RUNS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + runs: _containers.RepeatedCompositeFieldContainer[SyncRunProto] + total_count: int + def __init__(self, runs: _Optional[_Iterable[_Union[SyncRunProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class SyncRunProto(_message.Message): + __slots__ = ("id", "integration_id", "status", "items_synced", "duration_ms", "started_at", "completed_at", "error_code") + ID_FIELD_NUMBER: _ClassVar[int] + INTEGRATION_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + ITEMS_SYNCED_FIELD_NUMBER: _ClassVar[int] + DURATION_MS_FIELD_NUMBER: _ClassVar[int] + STARTED_AT_FIELD_NUMBER: _ClassVar[int] + COMPLETED_AT_FIELD_NUMBER: _ClassVar[int] + ERROR_CODE_FIELD_NUMBER: _ClassVar[int] + id: str + integration_id: str + status: str + items_synced: int + duration_ms: int + started_at: str + completed_at: str + error_code: SyncErrorCode + def __init__(self, id: _Optional[str] = ..., integration_id: _Optional[str] = ..., status: _Optional[str] = ..., items_synced: _Optional[int] = ..., duration_ms: _Optional[int] = ..., started_at: _Optional[str] = ..., completed_at: _Optional[str] = ..., error_code: _Optional[_Union[SyncErrorCode, str]] = ...) -> None: ... + +class GetUserIntegrationsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class IntegrationInfo(_message.Message): + __slots__ = ("id", "name", "type", "status", "workspace_id") + ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + id: str + name: str + type: str + status: str + workspace_id: str + def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., type: _Optional[str] = ..., status: _Optional[str] = ..., workspace_id: _Optional[str] = ...) -> None: ... + +class GetUserIntegrationsResponse(_message.Message): + __slots__ = ("integrations",) + INTEGRATIONS_FIELD_NUMBER: _ClassVar[int] + integrations: _containers.RepeatedCompositeFieldContainer[IntegrationInfo] + def __init__(self, integrations: _Optional[_Iterable[_Union[IntegrationInfo, _Mapping]]] = ...) -> None: ... + +class GetRecentLogsRequest(_message.Message): + __slots__ = ("limit", "level", "source") + LIMIT_FIELD_NUMBER: _ClassVar[int] + LEVEL_FIELD_NUMBER: _ClassVar[int] + SOURCE_FIELD_NUMBER: _ClassVar[int] + limit: int + level: str + source: str + def __init__(self, limit: _Optional[int] = ..., level: _Optional[str] = ..., source: _Optional[str] = ...) -> None: ... + +class GetRecentLogsResponse(_message.Message): + __slots__ = ("logs",) + LOGS_FIELD_NUMBER: _ClassVar[int] + logs: _containers.RepeatedCompositeFieldContainer[LogEntryProto] + def __init__(self, logs: _Optional[_Iterable[_Union[LogEntryProto, _Mapping]]] = ...) -> None: ... + +class LogEntryProto(_message.Message): + __slots__ = ("timestamp", "level", "source", "message", "details", "trace_id", "span_id", "event_type", "operation_id", "entity_id") + class DetailsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + LEVEL_FIELD_NUMBER: _ClassVar[int] + SOURCE_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + DETAILS_FIELD_NUMBER: _ClassVar[int] + TRACE_ID_FIELD_NUMBER: _ClassVar[int] + SPAN_ID_FIELD_NUMBER: _ClassVar[int] + EVENT_TYPE_FIELD_NUMBER: _ClassVar[int] + OPERATION_ID_FIELD_NUMBER: _ClassVar[int] + ENTITY_ID_FIELD_NUMBER: _ClassVar[int] + timestamp: str + level: str + source: str + message: str + details: _containers.ScalarMap[str, str] + trace_id: str + span_id: str + event_type: str + operation_id: str + entity_id: str + def __init__(self, timestamp: _Optional[str] = ..., level: _Optional[str] = ..., source: _Optional[str] = ..., message: _Optional[str] = ..., details: _Optional[_Mapping[str, str]] = ..., trace_id: _Optional[str] = ..., span_id: _Optional[str] = ..., event_type: _Optional[str] = ..., operation_id: _Optional[str] = ..., entity_id: _Optional[str] = ...) -> None: ... + +class GetPerformanceMetricsRequest(_message.Message): + __slots__ = ("history_limit",) + HISTORY_LIMIT_FIELD_NUMBER: _ClassVar[int] + history_limit: int + def __init__(self, history_limit: _Optional[int] = ...) -> None: ... + +class GetPerformanceMetricsResponse(_message.Message): + __slots__ = ("current", "history") + CURRENT_FIELD_NUMBER: _ClassVar[int] + HISTORY_FIELD_NUMBER: _ClassVar[int] + current: PerformanceMetricsPoint + history: _containers.RepeatedCompositeFieldContainer[PerformanceMetricsPoint] + def __init__(self, current: _Optional[_Union[PerformanceMetricsPoint, _Mapping]] = ..., history: _Optional[_Iterable[_Union[PerformanceMetricsPoint, _Mapping]]] = ...) -> None: ... + +class PerformanceMetricsPoint(_message.Message): + __slots__ = ("timestamp", "cpu_percent", "memory_percent", "memory_mb", "disk_percent", "network_bytes_sent", "network_bytes_recv", "process_memory_mb", "active_connections") + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + CPU_PERCENT_FIELD_NUMBER: _ClassVar[int] + MEMORY_PERCENT_FIELD_NUMBER: _ClassVar[int] + MEMORY_MB_FIELD_NUMBER: _ClassVar[int] + DISK_PERCENT_FIELD_NUMBER: _ClassVar[int] + NETWORK_BYTES_SENT_FIELD_NUMBER: _ClassVar[int] + NETWORK_BYTES_RECV_FIELD_NUMBER: _ClassVar[int] + PROCESS_MEMORY_MB_FIELD_NUMBER: _ClassVar[int] + ACTIVE_CONNECTIONS_FIELD_NUMBER: _ClassVar[int] + timestamp: float + cpu_percent: float + memory_percent: float + memory_mb: float + disk_percent: float + network_bytes_sent: int + network_bytes_recv: int + process_memory_mb: float + active_connections: int + def __init__(self, timestamp: _Optional[float] = ..., cpu_percent: _Optional[float] = ..., memory_percent: _Optional[float] = ..., memory_mb: _Optional[float] = ..., disk_percent: _Optional[float] = ..., network_bytes_sent: _Optional[int] = ..., network_bytes_recv: _Optional[int] = ..., process_memory_mb: _Optional[float] = ..., active_connections: _Optional[int] = ...) -> None: ... + +class ClaimMappingProto(_message.Message): + __slots__ = ("subject_claim", "email_claim", "email_verified_claim", "name_claim", "preferred_username_claim", "groups_claim", "picture_claim", "first_name_claim", "last_name_claim", "phone_claim") + SUBJECT_CLAIM_FIELD_NUMBER: _ClassVar[int] + EMAIL_CLAIM_FIELD_NUMBER: _ClassVar[int] + EMAIL_VERIFIED_CLAIM_FIELD_NUMBER: _ClassVar[int] + NAME_CLAIM_FIELD_NUMBER: _ClassVar[int] + PREFERRED_USERNAME_CLAIM_FIELD_NUMBER: _ClassVar[int] + GROUPS_CLAIM_FIELD_NUMBER: _ClassVar[int] + PICTURE_CLAIM_FIELD_NUMBER: _ClassVar[int] + FIRST_NAME_CLAIM_FIELD_NUMBER: _ClassVar[int] + LAST_NAME_CLAIM_FIELD_NUMBER: _ClassVar[int] + PHONE_CLAIM_FIELD_NUMBER: _ClassVar[int] + subject_claim: str + email_claim: str + email_verified_claim: str + name_claim: str + preferred_username_claim: str + groups_claim: str + picture_claim: str + first_name_claim: str + last_name_claim: str + phone_claim: str + def __init__(self, subject_claim: _Optional[str] = ..., email_claim: _Optional[str] = ..., email_verified_claim: _Optional[str] = ..., name_claim: _Optional[str] = ..., preferred_username_claim: _Optional[str] = ..., groups_claim: _Optional[str] = ..., picture_claim: _Optional[str] = ..., first_name_claim: _Optional[str] = ..., last_name_claim: _Optional[str] = ..., phone_claim: _Optional[str] = ...) -> None: ... + +class OidcDiscoveryProto(_message.Message): + __slots__ = ("issuer", "authorization_endpoint", "token_endpoint", "userinfo_endpoint", "jwks_uri", "end_session_endpoint", "revocation_endpoint", "scopes_supported", "claims_supported", "supports_pkce") + ISSUER_FIELD_NUMBER: _ClassVar[int] + AUTHORIZATION_ENDPOINT_FIELD_NUMBER: _ClassVar[int] + TOKEN_ENDPOINT_FIELD_NUMBER: _ClassVar[int] + USERINFO_ENDPOINT_FIELD_NUMBER: _ClassVar[int] + JWKS_URI_FIELD_NUMBER: _ClassVar[int] + END_SESSION_ENDPOINT_FIELD_NUMBER: _ClassVar[int] + REVOCATION_ENDPOINT_FIELD_NUMBER: _ClassVar[int] + SCOPES_SUPPORTED_FIELD_NUMBER: _ClassVar[int] + CLAIMS_SUPPORTED_FIELD_NUMBER: _ClassVar[int] + SUPPORTS_PKCE_FIELD_NUMBER: _ClassVar[int] + issuer: str + authorization_endpoint: str + token_endpoint: str + userinfo_endpoint: str + jwks_uri: str + end_session_endpoint: str + revocation_endpoint: str + scopes_supported: _containers.RepeatedScalarFieldContainer[str] + claims_supported: _containers.RepeatedScalarFieldContainer[str] + supports_pkce: bool + def __init__(self, issuer: _Optional[str] = ..., authorization_endpoint: _Optional[str] = ..., token_endpoint: _Optional[str] = ..., userinfo_endpoint: _Optional[str] = ..., jwks_uri: _Optional[str] = ..., end_session_endpoint: _Optional[str] = ..., revocation_endpoint: _Optional[str] = ..., scopes_supported: _Optional[_Iterable[str]] = ..., claims_supported: _Optional[_Iterable[str]] = ..., supports_pkce: bool = ...) -> None: ... + +class OidcProviderProto(_message.Message): + __slots__ = ("id", "workspace_id", "name", "preset", "issuer_url", "client_id", "enabled", "discovery", "claim_mapping", "scopes", "require_email_verified", "allowed_groups", "created_at", "updated_at", "discovery_refreshed_at", "warnings") + ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + PRESET_FIELD_NUMBER: _ClassVar[int] + ISSUER_URL_FIELD_NUMBER: _ClassVar[int] + CLIENT_ID_FIELD_NUMBER: _ClassVar[int] + ENABLED_FIELD_NUMBER: _ClassVar[int] + DISCOVERY_FIELD_NUMBER: _ClassVar[int] + CLAIM_MAPPING_FIELD_NUMBER: _ClassVar[int] + SCOPES_FIELD_NUMBER: _ClassVar[int] + REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: _ClassVar[int] + ALLOWED_GROUPS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + DISCOVERY_REFRESHED_AT_FIELD_NUMBER: _ClassVar[int] + WARNINGS_FIELD_NUMBER: _ClassVar[int] + id: str + workspace_id: str + name: str + preset: str + issuer_url: str + client_id: str + enabled: bool + discovery: OidcDiscoveryProto + claim_mapping: ClaimMappingProto + scopes: _containers.RepeatedScalarFieldContainer[str] + require_email_verified: bool + allowed_groups: _containers.RepeatedScalarFieldContainer[str] + created_at: int + updated_at: int + discovery_refreshed_at: int + warnings: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, id: _Optional[str] = ..., workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., preset: _Optional[str] = ..., issuer_url: _Optional[str] = ..., client_id: _Optional[str] = ..., enabled: bool = ..., discovery: _Optional[_Union[OidcDiscoveryProto, _Mapping]] = ..., claim_mapping: _Optional[_Union[ClaimMappingProto, _Mapping]] = ..., scopes: _Optional[_Iterable[str]] = ..., require_email_verified: bool = ..., allowed_groups: _Optional[_Iterable[str]] = ..., created_at: _Optional[int] = ..., updated_at: _Optional[int] = ..., discovery_refreshed_at: _Optional[int] = ..., warnings: _Optional[_Iterable[str]] = ...) -> None: ... + +class RegisterOidcProviderRequest(_message.Message): + __slots__ = ("workspace_id", "name", "issuer_url", "client_id", "client_secret", "preset", "scopes", "claim_mapping", "allowed_groups", "require_email_verified", "auto_discover") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + ISSUER_URL_FIELD_NUMBER: _ClassVar[int] + CLIENT_ID_FIELD_NUMBER: _ClassVar[int] + CLIENT_SECRET_FIELD_NUMBER: _ClassVar[int] + PRESET_FIELD_NUMBER: _ClassVar[int] + SCOPES_FIELD_NUMBER: _ClassVar[int] + CLAIM_MAPPING_FIELD_NUMBER: _ClassVar[int] + ALLOWED_GROUPS_FIELD_NUMBER: _ClassVar[int] + REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: _ClassVar[int] + AUTO_DISCOVER_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + name: str + issuer_url: str + client_id: str + client_secret: str + preset: str + scopes: _containers.RepeatedScalarFieldContainer[str] + claim_mapping: ClaimMappingProto + allowed_groups: _containers.RepeatedScalarFieldContainer[str] + require_email_verified: bool + auto_discover: bool + def __init__(self, workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., issuer_url: _Optional[str] = ..., client_id: _Optional[str] = ..., client_secret: _Optional[str] = ..., preset: _Optional[str] = ..., scopes: _Optional[_Iterable[str]] = ..., claim_mapping: _Optional[_Union[ClaimMappingProto, _Mapping]] = ..., allowed_groups: _Optional[_Iterable[str]] = ..., require_email_verified: bool = ..., auto_discover: bool = ...) -> None: ... + +class ListOidcProvidersRequest(_message.Message): + __slots__ = ("workspace_id", "enabled_only") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + ENABLED_ONLY_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + enabled_only: bool + def __init__(self, workspace_id: _Optional[str] = ..., enabled_only: bool = ...) -> None: ... + +class ListOidcProvidersResponse(_message.Message): + __slots__ = ("providers", "total_count") + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[OidcProviderProto] + total_count: int + def __init__(self, providers: _Optional[_Iterable[_Union[OidcProviderProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GetOidcProviderRequest(_message.Message): + __slots__ = ("provider_id",) + PROVIDER_ID_FIELD_NUMBER: _ClassVar[int] + provider_id: str + def __init__(self, provider_id: _Optional[str] = ...) -> None: ... + +class UpdateOidcProviderRequest(_message.Message): + __slots__ = ("provider_id", "name", "scopes", "claim_mapping", "allowed_groups", "require_email_verified", "enabled") + PROVIDER_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SCOPES_FIELD_NUMBER: _ClassVar[int] + CLAIM_MAPPING_FIELD_NUMBER: _ClassVar[int] + ALLOWED_GROUPS_FIELD_NUMBER: _ClassVar[int] + REQUIRE_EMAIL_VERIFIED_FIELD_NUMBER: _ClassVar[int] + ENABLED_FIELD_NUMBER: _ClassVar[int] + provider_id: str + name: str + scopes: _containers.RepeatedScalarFieldContainer[str] + claim_mapping: ClaimMappingProto + allowed_groups: _containers.RepeatedScalarFieldContainer[str] + require_email_verified: bool + enabled: bool + def __init__(self, provider_id: _Optional[str] = ..., name: _Optional[str] = ..., scopes: _Optional[_Iterable[str]] = ..., claim_mapping: _Optional[_Union[ClaimMappingProto, _Mapping]] = ..., allowed_groups: _Optional[_Iterable[str]] = ..., require_email_verified: bool = ..., enabled: bool = ...) -> None: ... + +class DeleteOidcProviderRequest(_message.Message): + __slots__ = ("provider_id",) + PROVIDER_ID_FIELD_NUMBER: _ClassVar[int] + provider_id: str + def __init__(self, provider_id: _Optional[str] = ...) -> None: ... + +class DeleteOidcProviderResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class RefreshOidcDiscoveryRequest(_message.Message): + __slots__ = ("provider_id", "workspace_id") + PROVIDER_ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + provider_id: str + workspace_id: str + def __init__(self, provider_id: _Optional[str] = ..., workspace_id: _Optional[str] = ...) -> None: ... + +class RefreshOidcDiscoveryResponse(_message.Message): + __slots__ = ("results", "success_count", "failure_count") + class ResultsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + RESULTS_FIELD_NUMBER: _ClassVar[int] + SUCCESS_COUNT_FIELD_NUMBER: _ClassVar[int] + FAILURE_COUNT_FIELD_NUMBER: _ClassVar[int] + results: _containers.ScalarMap[str, str] + success_count: int + failure_count: int + def __init__(self, results: _Optional[_Mapping[str, str]] = ..., success_count: _Optional[int] = ..., failure_count: _Optional[int] = ...) -> None: ... + +class ListOidcPresetsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class OidcPresetProto(_message.Message): + __slots__ = ("preset", "display_name", "description", "default_scopes", "documentation_url", "notes") + PRESET_FIELD_NUMBER: _ClassVar[int] + DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + DEFAULT_SCOPES_FIELD_NUMBER: _ClassVar[int] + DOCUMENTATION_URL_FIELD_NUMBER: _ClassVar[int] + NOTES_FIELD_NUMBER: _ClassVar[int] + preset: str + display_name: str + description: str + default_scopes: _containers.RepeatedScalarFieldContainer[str] + documentation_url: str + notes: str + def __init__(self, preset: _Optional[str] = ..., display_name: _Optional[str] = ..., description: _Optional[str] = ..., default_scopes: _Optional[_Iterable[str]] = ..., documentation_url: _Optional[str] = ..., notes: _Optional[str] = ...) -> None: ... + +class ListOidcPresetsResponse(_message.Message): + __slots__ = ("presets",) + PRESETS_FIELD_NUMBER: _ClassVar[int] + presets: _containers.RepeatedCompositeFieldContainer[OidcPresetProto] + def __init__(self, presets: _Optional[_Iterable[_Union[OidcPresetProto, _Mapping]]] = ...) -> None: ... + +class ExportRulesProto(_message.Message): + __slots__ = ("default_format", "include_audio", "include_timestamps", "template_id") + DEFAULT_FORMAT_FIELD_NUMBER: _ClassVar[int] + INCLUDE_AUDIO_FIELD_NUMBER: _ClassVar[int] + INCLUDE_TIMESTAMPS_FIELD_NUMBER: _ClassVar[int] + TEMPLATE_ID_FIELD_NUMBER: _ClassVar[int] + default_format: ExportFormat + include_audio: bool + include_timestamps: bool + template_id: str + def __init__(self, default_format: _Optional[_Union[ExportFormat, str]] = ..., include_audio: bool = ..., include_timestamps: bool = ..., template_id: _Optional[str] = ...) -> None: ... + +class TriggerRulesProto(_message.Message): + __slots__ = ("auto_start_enabled", "calendar_match_patterns", "app_match_patterns") + AUTO_START_ENABLED_FIELD_NUMBER: _ClassVar[int] + CALENDAR_MATCH_PATTERNS_FIELD_NUMBER: _ClassVar[int] + APP_MATCH_PATTERNS_FIELD_NUMBER: _ClassVar[int] + auto_start_enabled: bool + calendar_match_patterns: _containers.RepeatedScalarFieldContainer[str] + app_match_patterns: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, auto_start_enabled: bool = ..., calendar_match_patterns: _Optional[_Iterable[str]] = ..., app_match_patterns: _Optional[_Iterable[str]] = ...) -> None: ... + +class WorkspaceSettingsProto(_message.Message): + __slots__ = ("export_rules", "trigger_rules", "rag_enabled", "default_summarization_template") + EXPORT_RULES_FIELD_NUMBER: _ClassVar[int] + TRIGGER_RULES_FIELD_NUMBER: _ClassVar[int] + RAG_ENABLED_FIELD_NUMBER: _ClassVar[int] + DEFAULT_SUMMARIZATION_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + export_rules: ExportRulesProto + trigger_rules: TriggerRulesProto + rag_enabled: bool + default_summarization_template: str + def __init__(self, export_rules: _Optional[_Union[ExportRulesProto, _Mapping]] = ..., trigger_rules: _Optional[_Union[TriggerRulesProto, _Mapping]] = ..., rag_enabled: bool = ..., default_summarization_template: _Optional[str] = ...) -> None: ... + +class ProjectSettingsProto(_message.Message): + __slots__ = ("export_rules", "trigger_rules", "rag_enabled", "default_summarization_template") + EXPORT_RULES_FIELD_NUMBER: _ClassVar[int] + TRIGGER_RULES_FIELD_NUMBER: _ClassVar[int] + RAG_ENABLED_FIELD_NUMBER: _ClassVar[int] + DEFAULT_SUMMARIZATION_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + export_rules: ExportRulesProto + trigger_rules: TriggerRulesProto + rag_enabled: bool + default_summarization_template: str + def __init__(self, export_rules: _Optional[_Union[ExportRulesProto, _Mapping]] = ..., trigger_rules: _Optional[_Union[TriggerRulesProto, _Mapping]] = ..., rag_enabled: bool = ..., default_summarization_template: _Optional[str] = ...) -> None: ... + +class ProjectProto(_message.Message): + __slots__ = ("id", "workspace_id", "name", "slug", "description", "is_default", "is_archived", "settings", "created_at", "updated_at", "archived_at") + ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SLUG_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + IS_DEFAULT_FIELD_NUMBER: _ClassVar[int] + IS_ARCHIVED_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + ARCHIVED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + workspace_id: str + name: str + slug: str + description: str + is_default: bool + is_archived: bool + settings: ProjectSettingsProto + created_at: int + updated_at: int + archived_at: int + def __init__(self, id: _Optional[str] = ..., workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., slug: _Optional[str] = ..., description: _Optional[str] = ..., is_default: bool = ..., is_archived: bool = ..., settings: _Optional[_Union[ProjectSettingsProto, _Mapping]] = ..., created_at: _Optional[int] = ..., updated_at: _Optional[int] = ..., archived_at: _Optional[int] = ...) -> None: ... + +class ProjectMembershipProto(_message.Message): + __slots__ = ("project_id", "user_id", "role", "joined_at") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + JOINED_AT_FIELD_NUMBER: _ClassVar[int] + project_id: str + user_id: str + role: ProjectRoleProto + joined_at: int + def __init__(self, project_id: _Optional[str] = ..., user_id: _Optional[str] = ..., role: _Optional[_Union[ProjectRoleProto, str]] = ..., joined_at: _Optional[int] = ...) -> None: ... + +class CreateProjectRequest(_message.Message): + __slots__ = ("workspace_id", "name", "slug", "description", "settings") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SLUG_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + name: str + slug: str + description: str + settings: ProjectSettingsProto + def __init__(self, workspace_id: _Optional[str] = ..., name: _Optional[str] = ..., slug: _Optional[str] = ..., description: _Optional[str] = ..., settings: _Optional[_Union[ProjectSettingsProto, _Mapping]] = ...) -> None: ... + +class GetProjectRequest(_message.Message): + __slots__ = ("project_id",) + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + project_id: str + def __init__(self, project_id: _Optional[str] = ...) -> None: ... + +class GetProjectBySlugRequest(_message.Message): + __slots__ = ("workspace_id", "slug") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + SLUG_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + slug: str + def __init__(self, workspace_id: _Optional[str] = ..., slug: _Optional[str] = ...) -> None: ... + +class ListProjectsRequest(_message.Message): + __slots__ = ("workspace_id", "include_archived", "limit", "offset") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + INCLUDE_ARCHIVED_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + include_archived: bool + limit: int + offset: int + def __init__(self, workspace_id: _Optional[str] = ..., include_archived: bool = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListProjectsResponse(_message.Message): + __slots__ = ("projects", "total_count") + PROJECTS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + projects: _containers.RepeatedCompositeFieldContainer[ProjectProto] + total_count: int + def __init__(self, projects: _Optional[_Iterable[_Union[ProjectProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class UpdateProjectRequest(_message.Message): + __slots__ = ("project_id", "name", "slug", "description", "settings") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SLUG_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + project_id: str + name: str + slug: str + description: str + settings: ProjectSettingsProto + def __init__(self, project_id: _Optional[str] = ..., name: _Optional[str] = ..., slug: _Optional[str] = ..., description: _Optional[str] = ..., settings: _Optional[_Union[ProjectSettingsProto, _Mapping]] = ...) -> None: ... + +class ArchiveProjectRequest(_message.Message): + __slots__ = ("project_id",) + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + project_id: str + def __init__(self, project_id: _Optional[str] = ...) -> None: ... + +class RestoreProjectRequest(_message.Message): + __slots__ = ("project_id",) + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + project_id: str + def __init__(self, project_id: _Optional[str] = ...) -> None: ... + +class DeleteProjectRequest(_message.Message): + __slots__ = ("project_id",) + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + project_id: str + def __init__(self, project_id: _Optional[str] = ...) -> None: ... + +class DeleteProjectResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class SetActiveProjectRequest(_message.Message): + __slots__ = ("workspace_id", "project_id") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + project_id: str + def __init__(self, workspace_id: _Optional[str] = ..., project_id: _Optional[str] = ...) -> None: ... + +class SetActiveProjectResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetActiveProjectRequest(_message.Message): + __slots__ = ("workspace_id",) + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + def __init__(self, workspace_id: _Optional[str] = ...) -> None: ... + +class GetActiveProjectResponse(_message.Message): + __slots__ = ("project_id", "project") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_FIELD_NUMBER: _ClassVar[int] + project_id: str + project: ProjectProto + def __init__(self, project_id: _Optional[str] = ..., project: _Optional[_Union[ProjectProto, _Mapping]] = ...) -> None: ... + +class AddProjectMemberRequest(_message.Message): + __slots__ = ("project_id", "user_id", "role") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + project_id: str + user_id: str + role: ProjectRoleProto + def __init__(self, project_id: _Optional[str] = ..., user_id: _Optional[str] = ..., role: _Optional[_Union[ProjectRoleProto, str]] = ...) -> None: ... + +class UpdateProjectMemberRoleRequest(_message.Message): + __slots__ = ("project_id", "user_id", "role") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + project_id: str + user_id: str + role: ProjectRoleProto + def __init__(self, project_id: _Optional[str] = ..., user_id: _Optional[str] = ..., role: _Optional[_Union[ProjectRoleProto, str]] = ...) -> None: ... + +class RemoveProjectMemberRequest(_message.Message): + __slots__ = ("project_id", "user_id") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + project_id: str + user_id: str + def __init__(self, project_id: _Optional[str] = ..., user_id: _Optional[str] = ...) -> None: ... + +class RemoveProjectMemberResponse(_message.Message): + __slots__ = ("success",) + SUCCESS_FIELD_NUMBER: _ClassVar[int] + success: bool + def __init__(self, success: bool = ...) -> None: ... + +class ListProjectMembersRequest(_message.Message): + __slots__ = ("project_id", "limit", "offset") + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + project_id: str + limit: int + offset: int + def __init__(self, project_id: _Optional[str] = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListProjectMembersResponse(_message.Message): + __slots__ = ("members", "total_count") + MEMBERS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + members: _containers.RepeatedCompositeFieldContainer[ProjectMembershipProto] + total_count: int + def __init__(self, members: _Optional[_Iterable[_Union[ProjectMembershipProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class GetCurrentUserRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCurrentUserResponse(_message.Message): + __slots__ = ("user_id", "workspace_id", "display_name", "email", "is_authenticated", "auth_provider", "workspace_name", "role") + USER_ID_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int] + EMAIL_FIELD_NUMBER: _ClassVar[int] + IS_AUTHENTICATED_FIELD_NUMBER: _ClassVar[int] + AUTH_PROVIDER_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_NAME_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + user_id: str + workspace_id: str + display_name: str + email: str + is_authenticated: bool + auth_provider: str + workspace_name: str + role: str + def __init__(self, user_id: _Optional[str] = ..., workspace_id: _Optional[str] = ..., display_name: _Optional[str] = ..., email: _Optional[str] = ..., is_authenticated: bool = ..., auth_provider: _Optional[str] = ..., workspace_name: _Optional[str] = ..., role: _Optional[str] = ...) -> None: ... + +class WorkspaceProto(_message.Message): + __slots__ = ("id", "name", "slug", "is_default", "role") + ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SLUG_FIELD_NUMBER: _ClassVar[int] + IS_DEFAULT_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + id: str + name: str + slug: str + is_default: bool + role: str + def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., slug: _Optional[str] = ..., is_default: bool = ..., role: _Optional[str] = ...) -> None: ... + +class ListWorkspacesRequest(_message.Message): + __slots__ = ("limit", "offset") + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + limit: int + offset: int + def __init__(self, limit: _Optional[int] = ..., offset: _Optional[int] = ...) -> None: ... + +class ListWorkspacesResponse(_message.Message): + __slots__ = ("workspaces", "total_count") + WORKSPACES_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + workspaces: _containers.RepeatedCompositeFieldContainer[WorkspaceProto] + total_count: int + def __init__(self, workspaces: _Optional[_Iterable[_Union[WorkspaceProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class SwitchWorkspaceRequest(_message.Message): + __slots__ = ("workspace_id",) + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + def __init__(self, workspace_id: _Optional[str] = ...) -> None: ... + +class SwitchWorkspaceResponse(_message.Message): + __slots__ = ("success", "workspace", "error_message") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + WORKSPACE_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + success: bool + workspace: WorkspaceProto + error_message: str + def __init__(self, success: bool = ..., workspace: _Optional[_Union[WorkspaceProto, _Mapping]] = ..., error_message: _Optional[str] = ...) -> None: ... + +class GetWorkspaceSettingsRequest(_message.Message): + __slots__ = ("workspace_id",) + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + def __init__(self, workspace_id: _Optional[str] = ...) -> None: ... + +class UpdateWorkspaceSettingsRequest(_message.Message): + __slots__ = ("workspace_id", "settings") + WORKSPACE_ID_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + workspace_id: str + settings: WorkspaceSettingsProto + def __init__(self, workspace_id: _Optional[str] = ..., settings: _Optional[_Union[WorkspaceSettingsProto, _Mapping]] = ...) -> None: ... + +class TaskProto(_message.Message): + __slots__ = ("id", "meeting_id", "action_item_id", "text", "status", "assignee_person_id", "due_date", "priority", "completed_at", "created_at", "updated_at") + ID_FIELD_NUMBER: _ClassVar[int] + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + ACTION_ITEM_ID_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + ASSIGNEE_PERSON_ID_FIELD_NUMBER: _ClassVar[int] + DUE_DATE_FIELD_NUMBER: _ClassVar[int] + PRIORITY_FIELD_NUMBER: _ClassVar[int] + COMPLETED_AT_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + meeting_id: str + action_item_id: int + text: str + status: TaskStatusProto + assignee_person_id: str + due_date: float + priority: int + completed_at: float + created_at: float + updated_at: float + def __init__(self, id: _Optional[str] = ..., meeting_id: _Optional[str] = ..., action_item_id: _Optional[int] = ..., text: _Optional[str] = ..., status: _Optional[_Union[TaskStatusProto, str]] = ..., assignee_person_id: _Optional[str] = ..., due_date: _Optional[float] = ..., priority: _Optional[int] = ..., completed_at: _Optional[float] = ..., created_at: _Optional[float] = ..., updated_at: _Optional[float] = ...) -> None: ... + +class TaskWithMeetingProto(_message.Message): + __slots__ = ("task", "meeting_title", "meeting_created_at", "project_id") + TASK_FIELD_NUMBER: _ClassVar[int] + MEETING_TITLE_FIELD_NUMBER: _ClassVar[int] + MEETING_CREATED_AT_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + task: TaskProto + meeting_title: str + meeting_created_at: float + project_id: str + def __init__(self, task: _Optional[_Union[TaskProto, _Mapping]] = ..., meeting_title: _Optional[str] = ..., meeting_created_at: _Optional[float] = ..., project_id: _Optional[str] = ...) -> None: ... + +class ListTasksRequest(_message.Message): + __slots__ = ("statuses", "limit", "offset", "project_id", "project_ids", "meeting_id") + STATUSES_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_IDS_FIELD_NUMBER: _ClassVar[int] + MEETING_ID_FIELD_NUMBER: _ClassVar[int] + statuses: _containers.RepeatedScalarFieldContainer[TaskStatusProto] + limit: int + offset: int + project_id: str + project_ids: _containers.RepeatedScalarFieldContainer[str] + meeting_id: str + def __init__(self, statuses: _Optional[_Iterable[_Union[TaskStatusProto, str]]] = ..., limit: _Optional[int] = ..., offset: _Optional[int] = ..., project_id: _Optional[str] = ..., project_ids: _Optional[_Iterable[str]] = ..., meeting_id: _Optional[str] = ...) -> None: ... + +class ListTasksResponse(_message.Message): + __slots__ = ("tasks", "total_count") + TASKS_FIELD_NUMBER: _ClassVar[int] + TOTAL_COUNT_FIELD_NUMBER: _ClassVar[int] + tasks: _containers.RepeatedCompositeFieldContainer[TaskWithMeetingProto] + total_count: int + def __init__(self, tasks: _Optional[_Iterable[_Union[TaskWithMeetingProto, _Mapping]]] = ..., total_count: _Optional[int] = ...) -> None: ... + +class UpdateTaskRequest(_message.Message): + __slots__ = ("task_id", "text", "status", "assignee_person_id", "due_date", "priority") + TASK_ID_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + ASSIGNEE_PERSON_ID_FIELD_NUMBER: _ClassVar[int] + DUE_DATE_FIELD_NUMBER: _ClassVar[int] + PRIORITY_FIELD_NUMBER: _ClassVar[int] + task_id: str + text: str + status: TaskStatusProto + assignee_person_id: str + due_date: float + priority: int + def __init__(self, task_id: _Optional[str] = ..., text: _Optional[str] = ..., status: _Optional[_Union[TaskStatusProto, str]] = ..., assignee_person_id: _Optional[str] = ..., due_date: _Optional[float] = ..., priority: _Optional[int] = ...) -> None: ... + +class UpdateTaskResponse(_message.Message): + __slots__ = ("task",) + TASK_FIELD_NUMBER: _ClassVar[int] + task: TaskProto + def __init__(self, task: _Optional[_Union[TaskProto, _Mapping]] = ...) -> None: ... + +class GetAnalyticsOverviewRequest(_message.Message): + __slots__ = ("start_time", "end_time", "project_id", "project_ids") + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_IDS_FIELD_NUMBER: _ClassVar[int] + start_time: float + end_time: float + project_id: str + project_ids: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., project_id: _Optional[str] = ..., project_ids: _Optional[_Iterable[str]] = ...) -> None: ... + +class DailyMeetingStatsProto(_message.Message): + __slots__ = ("date", "meetings", "total_duration", "word_count") + DATE_FIELD_NUMBER: _ClassVar[int] + MEETINGS_FIELD_NUMBER: _ClassVar[int] + TOTAL_DURATION_FIELD_NUMBER: _ClassVar[int] + WORD_COUNT_FIELD_NUMBER: _ClassVar[int] + date: str + meetings: int + total_duration: float + word_count: int + def __init__(self, date: _Optional[str] = ..., meetings: _Optional[int] = ..., total_duration: _Optional[float] = ..., word_count: _Optional[int] = ...) -> None: ... + +class GetAnalyticsOverviewResponse(_message.Message): + __slots__ = ("daily", "total_meetings", "total_duration", "total_words", "total_segments", "speaker_count") + DAILY_FIELD_NUMBER: _ClassVar[int] + TOTAL_MEETINGS_FIELD_NUMBER: _ClassVar[int] + TOTAL_DURATION_FIELD_NUMBER: _ClassVar[int] + TOTAL_WORDS_FIELD_NUMBER: _ClassVar[int] + TOTAL_SEGMENTS_FIELD_NUMBER: _ClassVar[int] + SPEAKER_COUNT_FIELD_NUMBER: _ClassVar[int] + daily: _containers.RepeatedCompositeFieldContainer[DailyMeetingStatsProto] + total_meetings: int + total_duration: float + total_words: int + total_segments: int + speaker_count: int + def __init__(self, daily: _Optional[_Iterable[_Union[DailyMeetingStatsProto, _Mapping]]] = ..., total_meetings: _Optional[int] = ..., total_duration: _Optional[float] = ..., total_words: _Optional[int] = ..., total_segments: _Optional[int] = ..., speaker_count: _Optional[int] = ...) -> None: ... + +class SpeakerStatProto(_message.Message): + __slots__ = ("speaker_id", "display_name", "total_time", "segment_count", "meeting_count", "avg_confidence") + SPEAKER_ID_FIELD_NUMBER: _ClassVar[int] + DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int] + TOTAL_TIME_FIELD_NUMBER: _ClassVar[int] + SEGMENT_COUNT_FIELD_NUMBER: _ClassVar[int] + MEETING_COUNT_FIELD_NUMBER: _ClassVar[int] + AVG_CONFIDENCE_FIELD_NUMBER: _ClassVar[int] + speaker_id: str + display_name: str + total_time: float + segment_count: int + meeting_count: int + avg_confidence: float + def __init__(self, speaker_id: _Optional[str] = ..., display_name: _Optional[str] = ..., total_time: _Optional[float] = ..., segment_count: _Optional[int] = ..., meeting_count: _Optional[int] = ..., avg_confidence: _Optional[float] = ...) -> None: ... + +class ListSpeakerStatsRequest(_message.Message): + __slots__ = ("start_time", "end_time", "project_id", "project_ids") + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_IDS_FIELD_NUMBER: _ClassVar[int] + start_time: float + end_time: float + project_id: str + project_ids: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., project_id: _Optional[str] = ..., project_ids: _Optional[_Iterable[str]] = ...) -> None: ... + +class ListSpeakerStatsResponse(_message.Message): + __slots__ = ("speakers",) + SPEAKERS_FIELD_NUMBER: _ClassVar[int] + speakers: _containers.RepeatedCompositeFieldContainer[SpeakerStatProto] + def __init__(self, speakers: _Optional[_Iterable[_Union[SpeakerStatProto, _Mapping]]] = ...) -> None: ... + +class EntityCategoryStatProto(_message.Message): + __slots__ = ("category", "count", "total_mentions") + CATEGORY_FIELD_NUMBER: _ClassVar[int] + COUNT_FIELD_NUMBER: _ClassVar[int] + TOTAL_MENTIONS_FIELD_NUMBER: _ClassVar[int] + category: str + count: int + total_mentions: int + def __init__(self, category: _Optional[str] = ..., count: _Optional[int] = ..., total_mentions: _Optional[int] = ...) -> None: ... + +class TopEntityProto(_message.Message): + __slots__ = ("text", "category", "mention_count", "meeting_count") + TEXT_FIELD_NUMBER: _ClassVar[int] + CATEGORY_FIELD_NUMBER: _ClassVar[int] + MENTION_COUNT_FIELD_NUMBER: _ClassVar[int] + MEETING_COUNT_FIELD_NUMBER: _ClassVar[int] + text: str + category: str + mention_count: int + meeting_count: int + def __init__(self, text: _Optional[str] = ..., category: _Optional[str] = ..., mention_count: _Optional[int] = ..., meeting_count: _Optional[int] = ...) -> None: ... + +class GetEntityAnalyticsRequest(_message.Message): + __slots__ = ("start_time", "end_time", "project_id", "project_ids", "top_limit") + START_TIME_FIELD_NUMBER: _ClassVar[int] + END_TIME_FIELD_NUMBER: _ClassVar[int] + PROJECT_ID_FIELD_NUMBER: _ClassVar[int] + PROJECT_IDS_FIELD_NUMBER: _ClassVar[int] + TOP_LIMIT_FIELD_NUMBER: _ClassVar[int] + start_time: float + end_time: float + project_id: str + project_ids: _containers.RepeatedScalarFieldContainer[str] + top_limit: int + def __init__(self, start_time: _Optional[float] = ..., end_time: _Optional[float] = ..., project_id: _Optional[str] = ..., project_ids: _Optional[_Iterable[str]] = ..., top_limit: _Optional[int] = ...) -> None: ... + +class GetEntityAnalyticsResponse(_message.Message): + __slots__ = ("by_category", "top_entities", "total_entities", "total_mentions") + BY_CATEGORY_FIELD_NUMBER: _ClassVar[int] + TOP_ENTITIES_FIELD_NUMBER: _ClassVar[int] + TOTAL_ENTITIES_FIELD_NUMBER: _ClassVar[int] + TOTAL_MENTIONS_FIELD_NUMBER: _ClassVar[int] + by_category: _containers.RepeatedCompositeFieldContainer[EntityCategoryStatProto] + top_entities: _containers.RepeatedCompositeFieldContainer[TopEntityProto] + total_entities: int + total_mentions: int + def __init__(self, by_category: _Optional[_Iterable[_Union[EntityCategoryStatProto, _Mapping]]] = ..., top_entities: _Optional[_Iterable[_Union[TopEntityProto, _Mapping]]] = ..., total_entities: _Optional[int] = ..., total_mentions: _Optional[int] = ...) -> None: ... diff --git a/src/noteflow/grpc/proto/noteflow_pb2_grpc.py b/src/noteflow/grpc/proto/noteflow_pb2_grpc.py index da8cec9..e91d1fd 100644 --- a/src/noteflow/grpc/proto/noteflow_pb2_grpc.py +++ b/src/noteflow/grpc/proto/noteflow_pb2_grpc.py @@ -3,7 +3,7 @@ import grpc import warnings -import noteflow_pb2 as noteflow__pb2 +from . import noteflow_pb2 as noteflow__pb2 GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ @@ -453,6 +453,11 @@ class NoteFlowServiceStub(object): request_serializer=noteflow__pb2.ListSpeakerStatsRequest.SerializeToString, response_deserializer=noteflow__pb2.ListSpeakerStatsResponse.FromString, _registered_method=True) + self.GetEntityAnalytics = channel.unary_unary( + '/noteflow.NoteFlowService/GetEntityAnalytics', + request_serializer=noteflow__pb2.GetEntityAnalyticsRequest.SerializeToString, + response_deserializer=noteflow__pb2.GetEntityAnalyticsResponse.FromString, + _registered_method=True) self.AddProjectMember = channel.unary_unary( '/noteflow.NoteFlowService/AddProjectMember', request_serializer=noteflow__pb2.AddProjectMemberRequest.SerializeToString, @@ -1030,6 +1035,12 @@ class NoteFlowServiceServicer(object): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetEntityAnalytics(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def AddProjectMember(self, request, context): """Project membership management (Sprint 18) """ @@ -1504,6 +1515,11 @@ def add_NoteFlowServiceServicer_to_server(servicer, server): request_deserializer=noteflow__pb2.ListSpeakerStatsRequest.FromString, response_serializer=noteflow__pb2.ListSpeakerStatsResponse.SerializeToString, ), + 'GetEntityAnalytics': grpc.unary_unary_rpc_method_handler( + servicer.GetEntityAnalytics, + request_deserializer=noteflow__pb2.GetEntityAnalyticsRequest.FromString, + response_serializer=noteflow__pb2.GetEntityAnalyticsResponse.SerializeToString, + ), 'AddProjectMember': grpc.unary_unary_rpc_method_handler( servicer.AddProjectMember, request_deserializer=noteflow__pb2.AddProjectMemberRequest.FromString, @@ -3805,6 +3821,33 @@ class NoteFlowService(object): metadata, _registered_method=True) + @staticmethod + def GetEntityAnalytics(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/noteflow.NoteFlowService/GetEntityAnalytics', + noteflow__pb2.GetEntityAnalyticsRequest.SerializeToString, + noteflow__pb2.GetEntityAnalyticsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def AddProjectMember(request, target, diff --git a/src/noteflow/grpc/service.py b/src/noteflow/grpc/service.py index c71000b..ff0f70f 100644 --- a/src/noteflow/grpc/service.py +++ b/src/noteflow/grpc/service.py @@ -225,25 +225,28 @@ class NoteFlowServicer( async def _persist_asr_configuration(self, capabilities: AsrCapabilities) -> None: """Persist the active ASR configuration to preferences.""" - if not capabilities.model_size: + model_size = capabilities.model_size + if model_size is None: logger.warning("asr_config_persist_skipped_missing_model") return if self.session_factory is None: logger.debug("asr_config_persist_skipped_no_db") return - await self._persist_asr_configuration_to_db(capabilities) + await self._persist_asr_configuration_to_db(model_size, capabilities) async def _persist_asr_configuration_to_db( self, + model_size: str, capabilities: AsrCapabilities, ) -> None: try: - await self._write_asr_configuration_preference(capabilities) + await self._write_asr_configuration_preference(model_size, capabilities) except Exception as exc: logger.exception("asr_config_persist_failed", error=str(exc)) async def _write_asr_configuration_preference( self, + model_size: str, capabilities: AsrCapabilities, ) -> None: async with self.create_uow() as uow: @@ -251,7 +254,7 @@ class NoteFlowServicer( logger.debug("asr_config_persist_skipped_no_preferences") return preference = build_asr_config_preference( - capabilities.model_size, + model_size, capabilities.device.value, capabilities.compute_type.value, ) diff --git a/src/noteflow/grpc/servicer/info_mixin.py b/src/noteflow/grpc/servicer/info_mixin.py new file mode 100644 index 0000000..5ba9b60 --- /dev/null +++ b/src/noteflow/grpc/servicer/info_mixin.py @@ -0,0 +1,83 @@ +"""Mixin for server info operations.""" + +from __future__ import annotations + +import time +from typing import TYPE_CHECKING, ClassVar + +from noteflow.grpc.meeting_store import MeetingStore +from noteflow.infrastructure.metrics import get_system_resources + +from ..proto import noteflow_pb2 + +if TYPE_CHECKING: + from collections.abc import Awaitable, Callable + + from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker + + from noteflow.infrastructure.asr import FasterWhisperEngine + from noteflow.infrastructure.diarization.engine import DiarizationEngine + +from ..mixins._types import GrpcContext + + +class ServicerInfoMixin: + """Mixin for server info operations.""" + + asr_engine: FasterWhisperEngine | None + diarization_engine: DiarizationEngine | None + session_factory: async_sessionmaker[AsyncSession] | None + memory_store: MeetingStore | None + _start_time: float + SUPPORTED_SAMPLE_RATES: ClassVar[list[int]] + MAX_CHUNK_SIZE: ClassVar[int] + VERSION: ClassVar[str] + STATE_VERSION: ClassVar[int] + _count_active_meetings_db: Callable[..., Awaitable[int]] + get_memory_store: Callable[..., MeetingStore] + + async def GetServerInfo( + self, + request: noteflow_pb2.ServerInfoRequest, + context: GrpcContext, + ) -> noteflow_pb2.ServerInfo: + """Get server information.""" + asr_model = "" + asr_ready = False + if self.asr_engine: + asr_ready = self.asr_engine.is_loaded + asr_model = self.asr_engine.model_size or "" + + diarization_enabled = self.diarization_engine is not None + diarization_ready = self.diarization_engine is not None and ( + self.diarization_engine.is_streaming_loaded or self.diarization_engine.is_offline_loaded + ) + + if self.session_factory is not None: + active = await self._count_active_meetings_db() + else: + active = self.get_memory_store().active_count + + resources = get_system_resources() + response = noteflow_pb2.ServerInfo( + version=self.VERSION, + asr_model=asr_model, + asr_ready=asr_ready, + supported_sample_rates=self.SUPPORTED_SAMPLE_RATES, + max_chunk_size=self.MAX_CHUNK_SIZE, + uptime_seconds=time.time() - self._start_time, + active_meetings=active, + diarization_enabled=diarization_enabled, + diarization_ready=diarization_ready, + state_version=self.STATE_VERSION, + ) + if resources.ram_total_bytes is not None: + response.system_ram_total_bytes = resources.ram_total_bytes + if resources.ram_available_bytes is not None: + response.system_ram_available_bytes = resources.ram_available_bytes + if resources.gpu_vram_total_bytes is not None: + response.gpu_vram_total_bytes = resources.gpu_vram_total_bytes + if resources.gpu_vram_available_bytes is not None: + response.gpu_vram_available_bytes = resources.gpu_vram_available_bytes + + return response diff --git a/src/noteflow/grpc/servicer/mixins.py b/src/noteflow/grpc/servicer/mixins.py index 93d4646..f5beb18 100644 --- a/src/noteflow/grpc/servicer/mixins.py +++ b/src/noteflow/grpc/servicer/mixins.py @@ -1,4 +1,5 @@ """Mixin classes for NoteFlowServicer.""" + from __future__ import annotations import time @@ -23,11 +24,11 @@ from noteflow.infrastructure.logging import ( user_id_var, workspace_id_var, ) -from noteflow.infrastructure.metrics import get_system_resources from noteflow.infrastructure.persistence.memory import MemoryUnitOfWork from noteflow.infrastructure.persistence.unit_of_work import SqlAlchemyUnitOfWork from noteflow.infrastructure.security.crypto import AesGcmCryptoBox +from .info_mixin import ServicerInfoMixin from .shutdown import ( cancel_diarization_tasks, cancel_sync_tasks, @@ -37,26 +38,32 @@ from .shutdown import ( mark_in_memory_jobs_failed, mark_running_jobs_failed_db, ) -from ..proto import noteflow_pb2 from ..stream_state import MeetingStreamState +__all__ = [ + "ServicerAudioMixin", + "ServicerContextMixin", + "ServicerInfoMixin", + "ServicerLifecycleMixin", + "ServicerStreamingStateMixin", + "ServicerUowMixin", +] + if TYPE_CHECKING: - from collections.abc import Awaitable, Callable - from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker - from noteflow.infrastructure.asr import FasterWhisperEngine - from noteflow.infrastructure.diarization.engine import DiarizationEngine - from ..mixins._types import GrpcContext logger = get_logger(__name__) + + class ServicerUowMixin: """Mixin for Unit of Work operations.""" session_factory: async_sessionmaker[AsyncSession] | None meetings_dir: Path memory_store: MeetingStore | None + def use_database(self) -> bool: """Check if database persistence is configured.""" return self.session_factory is not None @@ -125,7 +132,8 @@ class ServicerStreamingStateMixin: chunk_receipt_times: dict[str, deque[float]] pending_chunks: dict[str, int] stop_requested: set[str] - DEFAULT_SAMPLE_RATE: int + DEFAULT_SAMPLE_RATE: ClassVar[int] + def init_streaming_state(self, meeting_id: str, next_segment_id: int) -> None: """Initialize VAD, Segmenter, speaking state, and partial buffers for a meeting.""" vad = StreamingVad() @@ -168,7 +176,9 @@ class ServicerStreamingStateMixin: def cleanup_streaming_state(self, meeting_id: str) -> None: """Clean up VAD, Segmenter, speaking state, and partial buffers for a meeting.""" - if (state := self.stream_states.pop(meeting_id, None)) and state.diarization_session is not None: + if ( + state := self.stream_states.pop(meeting_id, None) + ) and state.diarization_session is not None: state.diarization_session.close() self.vad_instances.pop(meeting_id, None) @@ -206,7 +216,7 @@ class ServicerAudioMixin: meetings_dir: Path audio_writers: dict[str, MeetingAudioWriter] audio_write_failed: set[str] - DEFAULT_SAMPLE_RATE: int + DEFAULT_SAMPLE_RATE: ClassVar[int] def ensure_meeting_dek(self, meeting: Meeting) -> tuple[bytes, bytes, bool]: """Ensure meeting has a DEK, generating one if needed.""" @@ -271,68 +281,6 @@ class ServicerAudioMixin: ) -class ServicerInfoMixin: - """Mixin for server info operations.""" - - asr_engine: FasterWhisperEngine | None - diarization_engine: DiarizationEngine | None - session_factory: async_sessionmaker[AsyncSession] | None - memory_store: MeetingStore | None - _start_time: float - SUPPORTED_SAMPLE_RATES: ClassVar[list[int]] - MAX_CHUNK_SIZE: int - VERSION: str - STATE_VERSION: int - _count_active_meetings_db: Callable[..., Awaitable[int]] - get_memory_store: Callable[..., MeetingStore] - - async def GetServerInfo( - self, - request: noteflow_pb2.ServerInfoRequest, - context: GrpcContext, - ) -> noteflow_pb2.ServerInfo: - """Get server information.""" - asr_model = "" - asr_ready = False - if self.asr_engine: - asr_ready = self.asr_engine.is_loaded - asr_model = self.asr_engine.model_size or "" - - diarization_enabled = self.diarization_engine is not None - diarization_ready = self.diarization_engine is not None and ( - self.diarization_engine.is_streaming_loaded or self.diarization_engine.is_offline_loaded - ) - - if self.session_factory is not None: - active = await self._count_active_meetings_db() - else: - active = self.get_memory_store().active_count - - resources = get_system_resources() - response = noteflow_pb2.ServerInfo( - version=self.VERSION, - asr_model=asr_model, - asr_ready=asr_ready, - supported_sample_rates=self.SUPPORTED_SAMPLE_RATES, - max_chunk_size=self.MAX_CHUNK_SIZE, - uptime_seconds=time.time() - self._start_time, - active_meetings=active, - diarization_enabled=diarization_enabled, - diarization_ready=diarization_ready, - state_version=self.STATE_VERSION, - ) - if resources.ram_total_bytes is not None: - response.system_ram_total_bytes = resources.ram_total_bytes - if resources.ram_available_bytes is not None: - response.system_ram_available_bytes = resources.ram_available_bytes - if resources.gpu_vram_total_bytes is not None: - response.gpu_vram_total_bytes = resources.gpu_vram_total_bytes - if resources.gpu_vram_available_bytes is not None: - response.gpu_vram_available_bytes = resources.gpu_vram_available_bytes - - return response - - class ServicerLifecycleMixin: """Mixin for servicer lifecycle operations.""" diff --git a/src/noteflow/infrastructure/converters/integration_converters.py b/src/noteflow/infrastructure/converters/integration_converters.py index 5ecbaf1..f9b8d08 100644 --- a/src/noteflow/infrastructure/converters/integration_converters.py +++ b/src/noteflow/infrastructure/converters/integration_converters.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from noteflow.domain.constants.fields import DURATION_MS, ENDED_AT +from noteflow.domain.constants.fields import DURATION_MS, ENDED_AT, ERROR_CODE from noteflow.domain.entities.integration import ( Integration, IntegrationStatus, @@ -121,6 +121,6 @@ class SyncRunConverter: "started_at": entity.started_at, ENDED_AT: entity.ended_at, DURATION_MS: entity.duration_ms, - "error_code": entity.error_code.value if entity.error_code else None, + ERROR_CODE: entity.error_code.value if entity.error_code else None, "stats": entity.stats, } diff --git a/src/noteflow/infrastructure/converters/orm_converters.py b/src/noteflow/infrastructure/converters/orm_converters.py index 16290a6..f9ac11a 100644 --- a/src/noteflow/infrastructure/converters/orm_converters.py +++ b/src/noteflow/infrastructure/converters/orm_converters.py @@ -7,6 +7,11 @@ from datetime import datetime from typing import TYPE_CHECKING, cast from noteflow.domain.constants.fields import END_TIME, START_TIME +from noteflow.domain.constants.processing import ( + PROCESSING_STEP_DIARIZATION, + PROCESSING_STEP_ENTITIES, + PROCESSING_STEP_SUMMARY, +) from noteflow.domain.entities import ( ActionItem, Annotation, @@ -96,13 +101,13 @@ class OrmConverter: if payload is None: return None summary = OrmConverter._processing_step_state_from_payload( - OrmConverter._coerce_step_payload(payload.get("summary")), + OrmConverter._coerce_step_payload(payload.get(PROCESSING_STEP_SUMMARY)), ) entities = OrmConverter._processing_step_state_from_payload( - OrmConverter._coerce_step_payload(payload.get("entities")), + OrmConverter._coerce_step_payload(payload.get(PROCESSING_STEP_ENTITIES)), ) diarization = OrmConverter._processing_step_state_from_payload( - OrmConverter._coerce_step_payload(payload.get("diarization")), + OrmConverter._coerce_step_payload(payload.get(PROCESSING_STEP_DIARIZATION)), ) return ProcessingStatus( summary=summary, @@ -134,16 +139,20 @@ class OrmConverter: if status is None: return None payload: dict[str, object] = { - "summary": OrmConverter._processing_step_state_to_payload(status.summary), - "entities": OrmConverter._processing_step_state_to_payload(status.entities), - "diarization": OrmConverter._processing_step_state_to_payload(status.diarization), + PROCESSING_STEP_SUMMARY: OrmConverter._processing_step_state_to_payload( + status.summary + ), + PROCESSING_STEP_ENTITIES: OrmConverter._processing_step_state_to_payload( + status.entities + ), + PROCESSING_STEP_DIARIZATION: OrmConverter._processing_step_state_to_payload( + status.diarization + ), } if status.queued_at is not None: payload["queued_at"] = status.queued_at.isoformat() return payload - # --- WordTiming --- - @staticmethod def word_timing_to_domain(model: WordTimingModel) -> DomainWordTiming: """Convert ORM WordTiming model to domain entity. @@ -188,8 +197,6 @@ class OrmConverter: "probability": word.probability, } - # --- Meeting --- - @staticmethod def meeting_to_domain(model: MeetingModel) -> Meeting: """Convert ORM Meeting model to domain entity. @@ -220,8 +227,6 @@ class OrmConverter: processing_status=OrmConverter.processing_status_from_payload(model.processing_status), ) - # --- Segment --- - @staticmethod def segment_to_domain(model: SegmentModel, include_words: bool = True) -> Segment: """Convert ORM Segment model to domain entity. @@ -256,8 +261,6 @@ class OrmConverter: db_id=model.id, ) - # --- Annotation --- - @staticmethod def annotation_to_domain(model: AnnotationModel) -> Annotation: """Convert ORM Annotation model to domain entity. @@ -280,8 +283,6 @@ class OrmConverter: db_id=model.id, ) - # --- Summary --- - @staticmethod def key_point_to_domain(model: KeyPointModel) -> KeyPoint: """Convert ORM KeyPoint model to domain entity. diff --git a/src/noteflow/infrastructure/observability/usage/_usage_event_builders.py b/src/noteflow/infrastructure/observability/usage/_usage_event_builders.py index 0cf77bd..1ac667c 100644 --- a/src/noteflow/infrastructure/observability/usage/_usage_event_builders.py +++ b/src/noteflow/infrastructure/observability/usage/_usage_event_builders.py @@ -11,6 +11,7 @@ from noteflow.application.observability.ports import ( ) from noteflow.config.constants import ERROR_DETAIL_PROJECT_ID from noteflow.domain.constants.fields import ( + ERROR_CODE, LATENCY_MS, MODEL_NAME, PROVIDER_NAME, @@ -29,7 +30,7 @@ def extract_event_context( meeting_id = cast(str | None, attributes.pop("meeting_id", None)) success = cast(bool, attributes.pop("success", True)) - error_code = cast(str | None, attributes.pop("error_code", None)) + error_code = cast(str | None, attributes.pop(ERROR_CODE, None)) return UsageEventContext(meeting_id=meeting_id, success=success, error_code=error_code), attributes @@ -84,7 +85,7 @@ def build_event_attributes(event: UsageEvent) -> dict[str, str | int | float | b (TOKENS_INPUT, event.tokens_input), (TOKENS_OUTPUT, event.tokens_output), (LATENCY_MS, event.latency_ms), - ("error_code", event.error_code), + (ERROR_CODE, event.error_code), ] # Build attributes from non-None field values diff --git a/src/noteflow/infrastructure/persistence/migrations/versions/w7x8y9z0a1b2_add_analytics_materialized_views.py b/src/noteflow/infrastructure/persistence/migrations/versions/w7x8y9z0a1b2_add_analytics_materialized_views.py new file mode 100644 index 0000000..ec94c12 --- /dev/null +++ b/src/noteflow/infrastructure/persistence/migrations/versions/w7x8y9z0a1b2_add_analytics_materialized_views.py @@ -0,0 +1,231 @@ +"""add_analytics_materialized_views + +Revision ID: w7x8y9z0a1b2 +Revises: v6w7x8y9z0a1 +Create Date: 2026-01-22 00:00:00.000000 + +""" + +from collections.abc import Sequence + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "w7x8y9z0a1b2" +down_revision: str | Sequence[str] | None = "v6w7x8y9z0a1" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Create materialized views for analytics aggregation.""" + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_daily_meeting_stats AS + WITH meeting_durations AS ( + SELECT + workspace_id, + project_id, + DATE(created_at) as meeting_date, + id as meeting_id, + CASE + WHEN started_at IS NOT NULL AND ended_at IS NOT NULL + THEN EXTRACT(EPOCH FROM ended_at) - EXTRACT(EPOCH FROM started_at) + ELSE 0 + END as duration_seconds + FROM noteflow.meetings + WHERE deleted_at IS NULL + ), + segment_word_counts AS ( + SELECT + s.meeting_id, + COALESCE(SUM( + CASE + WHEN s.text IS NULL OR s.text = '' THEN 0 + ELSE LENGTH(s.text) - LENGTH(REPLACE(s.text, ' ', '')) + 1 + END + ), 0) as word_count + FROM noteflow.segments s + GROUP BY s.meeting_id + ) + SELECT + md.workspace_id, + md.project_id, + md.meeting_date, + COUNT(md.meeting_id)::integer as meeting_count, + COALESCE(SUM(md.duration_seconds), 0)::float as total_duration_seconds, + COALESCE(SUM(swc.word_count), 0)::integer as word_count + FROM meeting_durations md + LEFT JOIN segment_word_counts swc ON swc.meeting_id = md.meeting_id + GROUP BY md.workspace_id, md.project_id, md.meeting_date + WITH DATA + """) + + # Index for efficient querying + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_daily_meeting_stats_unique + ON noteflow.mv_daily_meeting_stats (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid), meeting_date) + """) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_mv_daily_meeting_stats_workspace + ON noteflow.mv_daily_meeting_stats (workspace_id, meeting_date) + """) + + # Speaker stats materialized view + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_speaker_stats AS + SELECT + m.workspace_id, + m.project_id, + COALESCE(s.speaker_id, 'Unknown Speaker') as speaker_id, + COUNT(s.id) as segment_count, + COALESCE(SUM(s.end_time - s.start_time), 0) as total_time_seconds, + COUNT(DISTINCT s.meeting_id) as meeting_count, + COALESCE(AVG(s.speaker_confidence), 0) as avg_confidence + FROM noteflow.segments s + JOIN noteflow.meetings m ON s.meeting_id = m.id + WHERE m.deleted_at IS NULL + GROUP BY m.workspace_id, m.project_id, COALESCE(s.speaker_id, 'Unknown Speaker') + WITH DATA + """) + + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_speaker_stats_unique + ON noteflow.mv_speaker_stats (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid), speaker_id) + """) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_mv_speaker_stats_workspace + ON noteflow.mv_speaker_stats (workspace_id) + """) + + # Entity category stats materialized view + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_entity_category_stats AS + SELECT + m.workspace_id, + m.project_id, + ne.category, + COUNT(DISTINCT ne.id) as entity_count, + COALESCE(SUM(CARDINALITY(ne.segment_ids)), 0) as total_mentions + FROM noteflow.named_entities ne + JOIN noteflow.meetings m ON ne.meeting_id = m.id + WHERE m.deleted_at IS NULL + GROUP BY m.workspace_id, m.project_id, ne.category + WITH DATA + """) + + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_entity_category_stats_unique + ON noteflow.mv_entity_category_stats (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid), category) + """) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_mv_entity_category_stats_workspace + ON noteflow.mv_entity_category_stats (workspace_id) + """) + + # Top entities materialized view (aggregated by normalized_text + category) + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_top_entities AS + SELECT + m.workspace_id, + m.project_id, + ne.normalized_text as text, + ne.category, + COALESCE(SUM(CARDINALITY(ne.segment_ids)), 0) as mention_count, + COUNT(DISTINCT ne.meeting_id) as meeting_count + FROM noteflow.named_entities ne + JOIN noteflow.meetings m ON ne.meeting_id = m.id + WHERE m.deleted_at IS NULL + GROUP BY m.workspace_id, m.project_id, ne.normalized_text, ne.category + WITH DATA + """) + + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_top_entities_unique + ON noteflow.mv_top_entities (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid), text, category) + """) + op.execute(""" + CREATE INDEX IF NOT EXISTS ix_mv_top_entities_mentions + ON noteflow.mv_top_entities (workspace_id, mention_count DESC) + """) + + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_meeting_totals AS + WITH meeting_stats AS ( + SELECT + workspace_id, + project_id, + id as meeting_id, + CASE + WHEN started_at IS NOT NULL AND ended_at IS NOT NULL + THEN EXTRACT(EPOCH FROM ended_at) - EXTRACT(EPOCH FROM started_at) + ELSE 0 + END as duration_seconds + FROM noteflow.meetings + WHERE deleted_at IS NULL + ), + segment_stats AS ( + SELECT + m.workspace_id, + m.project_id, + COUNT(s.id) as total_segments, + COUNT(DISTINCT COALESCE(s.speaker_id, 'unknown')) as speaker_count, + COALESCE(SUM( + CASE + WHEN s.text IS NULL OR s.text = '' THEN 0 + ELSE LENGTH(s.text) - LENGTH(REPLACE(s.text, ' ', '')) + 1 + END + ), 0) as total_words + FROM noteflow.meetings m + LEFT JOIN noteflow.segments s ON s.meeting_id = m.id + WHERE m.deleted_at IS NULL + GROUP BY m.workspace_id, m.project_id + ) + SELECT + ms.workspace_id, + ms.project_id, + COUNT(ms.meeting_id)::integer as total_meetings, + COALESCE(SUM(ms.duration_seconds), 0)::float as total_duration_seconds, + COALESCE(ss.total_segments, 0)::integer as total_segments, + COALESCE(ss.speaker_count, 0)::integer as speaker_count, + COALESCE(ss.total_words, 0)::integer as total_words + FROM meeting_stats ms + LEFT JOIN segment_stats ss ON ss.workspace_id = ms.workspace_id + AND (ss.project_id = ms.project_id OR (ss.project_id IS NULL AND ms.project_id IS NULL)) + GROUP BY ms.workspace_id, ms.project_id, ss.total_segments, ss.speaker_count, ss.total_words + WITH DATA + """) + + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_meeting_totals_unique + ON noteflow.mv_meeting_totals (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid)) + """) + + # Entity totals materialized view + op.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS noteflow.mv_entity_totals AS + SELECT + m.workspace_id, + m.project_id, + COUNT(DISTINCT ne.id) as total_entities, + COALESCE(SUM(CARDINALITY(ne.segment_ids)), 0) as total_mentions + FROM noteflow.named_entities ne + JOIN noteflow.meetings m ON ne.meeting_id = m.id + WHERE m.deleted_at IS NULL + GROUP BY m.workspace_id, m.project_id + WITH DATA + """) + + op.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS ix_mv_entity_totals_unique + ON noteflow.mv_entity_totals (workspace_id, COALESCE(project_id, '00000000-0000-0000-0000-000000000000'::uuid)) + """) + + +def downgrade() -> None: + """Drop analytics materialized views.""" + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_entity_totals CASCADE") + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_meeting_totals CASCADE") + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_top_entities CASCADE") + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_entity_category_stats CASCADE") + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_speaker_stats CASCADE") + op.execute("DROP MATERIALIZED VIEW IF EXISTS noteflow.mv_daily_meeting_stats CASCADE") diff --git a/src/noteflow/infrastructure/persistence/repositories/_analytics_converters.py b/src/noteflow/infrastructure/persistence/repositories/_analytics_converters.py new file mode 100644 index 0000000..d6fd988 --- /dev/null +++ b/src/noteflow/infrastructure/persistence/repositories/_analytics_converters.py @@ -0,0 +1,112 @@ +"""Analytics row-to-domain converters.""" + +from __future__ import annotations + +from noteflow.domain.entities.analytics import ( + DailyMeetingStats, + EntityCategoryStat, + SpeakerStat, + TopEntity, +) + +from ._analytics_queries import CategoryStatsRow, DailyStatsRow, SpeakerStatsRow, TopEntityRow +from ._materialized_view_queries import ( + DailyStatsMVRow, + EntityCategoryMVRow, + SpeakerStatsMVRow, + TopEntityMVRow, +) + +__all__ = [ + "build_daily_stats", + "category_stat_from_mv", + "category_stat_from_row", + "daily_stat_from_mv", + "speaker_stat_from_mv", + "speaker_stat_from_row", + "top_entity_from_mv", + "top_entity_from_row", +] + + +def build_daily_stats( + daily_rows: list[DailyStatsRow], + word_counts_by_date: dict[str, int], +) -> list[DailyMeetingStats]: + daily_stats: list[DailyMeetingStats] = [] + for row in daily_rows: + date_str = str(row.date) + daily_stats.append( + DailyMeetingStats( + date=date_str, + meetings=int(row.meetings), + total_duration=float(row.total_duration), + word_count=word_counts_by_date.get(date_str, 0), + ) + ) + return daily_stats + + +def speaker_stat_from_row(row: SpeakerStatsRow) -> SpeakerStat: + return SpeakerStat( + speaker_id=str(row.speaker_id), + display_name=str(row.speaker_id), + total_time=float(row.total_time), + segment_count=int(row.segment_count), + meeting_count=int(row.meeting_count), + avg_confidence=float(row.avg_confidence), + ) + + +def category_stat_from_row(row: CategoryStatsRow) -> EntityCategoryStat: + return EntityCategoryStat( + category=str(row.category), + count=int(row.entity_count), + total_mentions=int(row.total_mentions), + ) + + +def top_entity_from_row(row: TopEntityRow) -> TopEntity: + return TopEntity( + text=str(row.text), + category=str(row.category), + mention_count=int(row.mention_count), + meeting_count=int(row.meeting_count), + ) + + +def daily_stat_from_mv(row: DailyStatsMVRow) -> DailyMeetingStats: + return DailyMeetingStats( + date=str(row.meeting_date), + meetings=int(row.meeting_count), + total_duration=float(row.total_duration_seconds), + word_count=int(row.word_count), + ) + + +def speaker_stat_from_mv(row: SpeakerStatsMVRow) -> SpeakerStat: + return SpeakerStat( + speaker_id=str(row.speaker_id), + display_name=str(row.speaker_id), + total_time=float(row.total_time_seconds), + segment_count=int(row.segment_count), + meeting_count=int(row.meeting_count), + avg_confidence=float(row.avg_confidence), + ) + + +def category_stat_from_mv(row: EntityCategoryMVRow) -> EntityCategoryStat: + return EntityCategoryStat( + category=str(row.category), + count=int(row.entity_count), + total_mentions=int(row.total_mentions), + ) + + +def top_entity_from_mv(row: TopEntityMVRow) -> TopEntity: + return TopEntity( + text=str(row.text), + category=str(row.category), + mention_count=int(row.mention_count), + meeting_count=int(row.meeting_count), + ) diff --git a/src/noteflow/infrastructure/persistence/repositories/_analytics_entity_queries.py b/src/noteflow/infrastructure/persistence/repositories/_analytics_entity_queries.py new file mode 100644 index 0000000..6b62f11 --- /dev/null +++ b/src/noteflow/infrastructure/persistence/repositories/_analytics_entity_queries.py @@ -0,0 +1,158 @@ +"""SQLAlchemy analytics entity query helpers.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Protocol, cast as typing_cast +from uuid import UUID + +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.sql import Select +from sqlalchemy.sql.elements import ColumnElement + +from noteflow.domain.constants.fields import CATEGORY +from noteflow.infrastructure.persistence.models import MeetingModel, NamedEntityModel + +__all__ = [ + "CategoryStatsRow", + "CategoryStatsSelect", + "DEFAULT_TOP_ENTITIES_LIMIT", + "EntityTotalsRow", + "EntityTotalsSelect", + "TopEntityRow", + "TopEntitySelect", + "category_stats_stmt", + "entity_conditions", + "entity_totals_stmt", + "fetch_category_rows", + "fetch_entity_totals_row", + "fetch_top_entity_rows", + "top_entities_stmt", +] + + +class CategoryStatsRow(Protocol): + category: str + entity_count: int + total_mentions: int + + +class TopEntityRow(Protocol): + text: str + category: str + mention_count: int + meeting_count: int + + +class EntityTotalsRow(Protocol): + total_entities: int + total_mentions: int + + +CategoryStatsSelect = Select[tuple[str, int, int]] +TopEntitySelect = Select[tuple[str, str, int, int]] +EntityTotalsSelect = Select[tuple[int, int]] + + +def entity_conditions( + workspace_id: UUID, + project_ids: list[UUID] | None, + start_time: datetime | None, + end_time: datetime | None, +) -> list[ColumnElement[bool]]: + conditions: list[ColumnElement[bool]] = [ + NamedEntityModel.meeting_id == MeetingModel.id, + MeetingModel.workspace_id == workspace_id, + MeetingModel.deleted_at.is_(None), + ] + if project_ids: + conditions.append(MeetingModel.project_id.in_(project_ids)) + if start_time: + conditions.append(MeetingModel.created_at >= start_time) + if end_time: + conditions.append(MeetingModel.created_at < end_time) + return conditions + + +def category_stats_stmt(conditions: list[ColumnElement[bool]]) -> CategoryStatsSelect: + entity_count = func.count(NamedEntityModel.id.distinct()).label("entity_count") + total_mentions = func.coalesce( + func.sum(func.cardinality(NamedEntityModel.segment_ids)), 0 + ).label("total_mentions") + return ( + select( + NamedEntityModel.category.label(CATEGORY), + entity_count, + total_mentions, + ) + .select_from(NamedEntityModel) + .join(MeetingModel, NamedEntityModel.meeting_id == MeetingModel.id) + .where(*conditions) + .group_by(NamedEntityModel.category) + .order_by(func.count(NamedEntityModel.id.distinct()).desc()) + ) + + +DEFAULT_TOP_ENTITIES_LIMIT = 20 + + +def top_entities_stmt( + conditions: list[ColumnElement[bool]], + limit: int = DEFAULT_TOP_ENTITIES_LIMIT, +) -> TopEntitySelect: + mention_count = func.coalesce( + func.sum(func.cardinality(NamedEntityModel.segment_ids)), 0 + ).label("mention_count") + meeting_count = func.count(NamedEntityModel.meeting_id.distinct()).label("meeting_count") + return ( + select( + NamedEntityModel.normalized_text.label("text"), + NamedEntityModel.category.label(CATEGORY), + mention_count, + meeting_count, + ) + .select_from(NamedEntityModel) + .join(MeetingModel, NamedEntityModel.meeting_id == MeetingModel.id) + .where(*conditions) + .group_by(NamedEntityModel.normalized_text, NamedEntityModel.category) + .order_by(mention_count.desc()) + .limit(limit) + ) + + +def entity_totals_stmt(conditions: list[ColumnElement[bool]]) -> EntityTotalsSelect: + total_entities = func.count(NamedEntityModel.id.distinct()).label("total_entities") + total_mentions = func.coalesce( + func.sum(func.cardinality(NamedEntityModel.segment_ids)), 0 + ).label("total_mentions") + return ( + select(total_entities, total_mentions) + .select_from(NamedEntityModel) + .join(MeetingModel, NamedEntityModel.meeting_id == MeetingModel.id) + .where(*conditions) + ) + + +async def fetch_category_rows( + session: AsyncSession, + stmt: CategoryStatsSelect, +) -> list[CategoryStatsRow]: + result = await session.execute(stmt) + return typing_cast(list[CategoryStatsRow], result.all()) + + +async def fetch_top_entity_rows( + session: AsyncSession, + stmt: TopEntitySelect, +) -> list[TopEntityRow]: + result = await session.execute(stmt) + return typing_cast(list[TopEntityRow], result.all()) + + +async def fetch_entity_totals_row( + session: AsyncSession, + stmt: EntityTotalsSelect, +) -> EntityTotalsRow: + result = await session.execute(stmt) + return typing_cast(EntityTotalsRow, result.one()) diff --git a/src/noteflow/infrastructure/persistence/repositories/_analytics_queries.py b/src/noteflow/infrastructure/persistence/repositories/_analytics_queries.py index 8f7b192..ae87c8c 100644 --- a/src/noteflow/infrastructure/persistence/repositories/_analytics_queries.py +++ b/src/noteflow/infrastructure/persistence/repositories/_analytics_queries.py @@ -1,9 +1,9 @@ -"""SQLAlchemy analytics query helpers.""" +"""SQLAlchemy analytics meeting query helpers.""" from __future__ import annotations from datetime import date, datetime -from typing import Protocol, cast as typing_cast +from typing import Final, Protocol, cast as typing_cast from uuid import UUID from sqlalchemy import Date, Float, cast as sa_cast, func, select @@ -14,23 +14,60 @@ from sqlalchemy.sql.elements import ColumnElement from noteflow.domain.constants.fields import DATE from noteflow.infrastructure.persistence.models import MeetingModel, SegmentModel +EPOCH: Final[str] = "epoch" + +from ._analytics_entity_queries import ( + CategoryStatsRow as CategoryStatsRow, + CategoryStatsSelect as CategoryStatsSelect, + DEFAULT_TOP_ENTITIES_LIMIT as DEFAULT_TOP_ENTITIES_LIMIT, + EntityTotalsRow as EntityTotalsRow, + EntityTotalsSelect as EntityTotalsSelect, + TopEntityRow as TopEntityRow, + TopEntitySelect as TopEntitySelect, + category_stats_stmt as category_stats_stmt, + entity_conditions as entity_conditions, + entity_totals_stmt as entity_totals_stmt, + fetch_category_rows as fetch_category_rows, + fetch_entity_totals_row as fetch_entity_totals_row, + fetch_top_entity_rows as fetch_top_entity_rows, + top_entities_stmt as top_entities_stmt, +) + __all__ = [ + "CategoryStatsRow", + "CategoryStatsSelect", + "DEFAULT_TOP_ENTITIES_LIMIT", "DailyStatsRow", + "DailyStatsSelect", + "EntityTotalsRow", + "EntityTotalsSelect", "SegmentTotalsRow", + "SegmentTotalsSelect", "SpeakerStatsRow", + "SpeakerStatsSelect", + "TopEntityRow", + "TopEntitySelect", "TotalsRow", + "TotalsSelect", "WordCountRow", + "WordCountSelect", + "category_stats_stmt", "daily_stats_stmt", + "entity_conditions", + "entity_totals_stmt", + "fetch_category_rows", "fetch_daily_rows", + "fetch_entity_totals_row", "fetch_segment_totals_row", "fetch_speaker_rows", + "fetch_top_entity_rows", "fetch_totals_row", "fetch_word_counts", "meeting_conditions", "segment_conditions", "segment_totals_stmt", - "speaker_conditions", "speaker_stats_stmt", + "top_entities_stmt", "totals_stmt", "word_count_stmt", ] @@ -59,7 +96,7 @@ class SegmentTotalsRow(Protocol): class SpeakerStatsRow(Protocol): - speaker_id: str | None + speaker_id: str segment_count: int total_time: float meeting_count: int @@ -70,7 +107,7 @@ DailyStatsSelect = Select[tuple[date, int, float]] WordCountSelect = Select[tuple[date, int]] TotalsSelect = Select[tuple[int, float]] SegmentTotalsSelect = Select[tuple[int, int, int]] -SpeakerStatsSelect = Select[tuple[str | None, int, float, int, float]] +SpeakerStatsSelect = Select[tuple[str, int, float, int, float]] def meeting_conditions( @@ -112,30 +149,21 @@ def segment_conditions( return conditions -def speaker_conditions( - workspace_id: UUID, - project_ids: list[UUID] | None, - start_time: datetime | None, - end_time: datetime | None, -) -> list[ColumnElement[bool]]: - conditions = segment_conditions(workspace_id, project_ids, start_time, end_time) - conditions.append(SegmentModel.speaker_id.isnot(None)) - return conditions - - def daily_stats_stmt(conditions: list[ColumnElement[bool]]) -> DailyStatsSelect: # Cast required: SQLAlchemy label typing loses concrete value types. meeting_date = sa_cast(func.date(MeetingModel.created_at), Date).label(DATE) meeting_count = func.count(MeetingModel.id.distinct()).label("meetings") + duration_expr = func.case( + ( + (MeetingModel.started_at.isnot(None)) & (MeetingModel.ended_at.isnot(None)), + func.extract(EPOCH, MeetingModel.ended_at) + - func.extract(EPOCH, MeetingModel.started_at), + ), + else_=0.0, + ) total_duration_expr = typing_cast( ColumnElement[float], - sa_cast( - func.sum( - func.extract("epoch", MeetingModel.ended_at) - - func.extract("epoch", MeetingModel.started_at) - ), - Float, - ), + sa_cast(func.sum(duration_expr), Float), ) total_duration = func.coalesce(total_duration_expr, 0.0).label("total_duration") return ( @@ -145,8 +173,6 @@ def daily_stats_stmt(conditions: list[ColumnElement[bool]]) -> DailyStatsSelect: total_duration, ) .where(*conditions) - .where(MeetingModel.started_at.isnot(None)) - .where(MeetingModel.ended_at.isnot(None)) .group_by(func.date(MeetingModel.created_at)) .order_by(func.date(MeetingModel.created_at)) ) @@ -155,21 +181,27 @@ def daily_stats_stmt(conditions: list[ColumnElement[bool]]) -> DailyStatsSelect: def word_count_stmt(conditions: list[ColumnElement[bool]]) -> WordCountSelect: # Cast required: SQLAlchemy label typing loses concrete value types. meeting_date = sa_cast(func.date(MeetingModel.created_at), Date).label(DATE) - word_count = func.coalesce( - func.sum( + # Word count: count spaces + 1, but only if text is non-empty + # CASE WHEN text = '' OR text IS NULL THEN 0 ELSE (length - spaces + 1) + word_count_expr = func.case( + ( + (SegmentModel.text == "") | (SegmentModel.text.is_(None)), + 0, + ), + else_=( func.length(SegmentModel.text) - func.length(func.replace(SegmentModel.text, " ", "")) + 1 ), - 0, - ).label("word_count") + ) + word_count = func.coalesce(func.sum(word_count_expr), 0).label("word_count") return ( select( meeting_date, word_count, ) .select_from(MeetingModel) - .join(SegmentModel, SegmentModel.meeting_id == MeetingModel.id) + .outerjoin(SegmentModel, SegmentModel.meeting_id == MeetingModel.id) .where(*conditions) .group_by(func.date(MeetingModel.created_at)) ) @@ -182,8 +214,8 @@ def totals_stmt(conditions: list[ColumnElement[bool]]) -> TotalsSelect: ColumnElement[float], sa_cast( func.sum( - func.extract("epoch", MeetingModel.ended_at) - - func.extract("epoch", MeetingModel.started_at) + func.extract(EPOCH, MeetingModel.ended_at) + - func.extract(EPOCH, MeetingModel.started_at) ), Float, ), @@ -201,18 +233,25 @@ def totals_stmt(conditions: list[ColumnElement[bool]]) -> TotalsSelect: def segment_totals_stmt(conditions: list[ColumnElement[bool]]) -> SegmentTotalsSelect: - total_words = func.coalesce( - func.sum( + word_count_expr = func.case( + ( + (SegmentModel.text == "") | (SegmentModel.text.is_(None)), + 0, + ), + else_=( func.length(SegmentModel.text) - func.length(func.replace(SegmentModel.text, " ", "")) + 1 ), - 0, - ).label("total_words") + ) + total_words = func.coalesce(func.sum(word_count_expr), 0).label("total_words") + speaker_count = func.count(func.coalesce(SegmentModel.speaker_id, "unknown").distinct()).label( + "speaker_count" + ) stmt = ( select( func.count(SegmentModel.id).label("total_segments"), - func.count(SegmentModel.speaker_id.distinct()).label("speaker_count"), + speaker_count, total_words, ) .select_from(SegmentModel) @@ -224,7 +263,8 @@ def segment_totals_stmt(conditions: list[ColumnElement[bool]]) -> SegmentTotalsS def speaker_stats_stmt(conditions: list[ColumnElement[bool]]) -> SpeakerStatsSelect: # Cast required: SQLAlchemy label typing loses concrete value types. - speaker_id = SegmentModel.speaker_id.label("speaker_id") + speaker_id_coalesced = func.coalesce(SegmentModel.speaker_id, "Unknown Speaker") + speaker_id = speaker_id_coalesced.label("speaker_id") segment_count = func.count(SegmentModel.id).label("segment_count") total_time = func.coalesce( func.sum(SegmentModel.end_time - SegmentModel.start_time), @@ -247,7 +287,7 @@ def speaker_stats_stmt(conditions: list[ColumnElement[bool]]) -> SpeakerStatsSel .select_from(SegmentModel) .join(MeetingModel, SegmentModel.meeting_id == MeetingModel.id) .where(*conditions) - .group_by(SegmentModel.speaker_id) + .group_by(speaker_id_coalesced) .order_by(func.sum(SegmentModel.end_time - SegmentModel.start_time).desc()) ) diff --git a/src/noteflow/infrastructure/persistence/repositories/_materialized_view_queries.py b/src/noteflow/infrastructure/persistence/repositories/_materialized_view_queries.py new file mode 100644 index 0000000..2ef6bfb --- /dev/null +++ b/src/noteflow/infrastructure/persistence/repositories/_materialized_view_queries.py @@ -0,0 +1,321 @@ +"""SQLAlchemy queries for analytics materialized views.""" + +from __future__ import annotations + +from datetime import date, datetime +from typing import Final, Literal, Protocol, cast as typing_cast, get_args +from uuid import UUID + +from sqlalchemy import text +from sqlalchemy.ext.asyncio import AsyncSession + +from noteflow.domain.constants.fields import PROJECT_IDS +from noteflow.domain.value_objects import AnalyticsQueryParams + +__all__ = [ + "ALL_MATERIALIZED_VIEWS", + "DailyStatsMVRow", + "EntityCategoryMVRow", + "EntityTotalsMVRow", + "MaterializedViewName", + "MeetingTotalsMVRow", + "SpeakerStatsMVRow", + "TopEntityMVRow", + "fetch_daily_stats_mv", + "fetch_entity_category_mv", + "fetch_entity_totals_mv", + "fetch_meeting_totals_mv", + "fetch_speaker_stats_mv", + "fetch_top_entities_mv", + "materialized_views_exist", + "refresh_all_materialized_views", + "refresh_materialized_view", +] + +# Type-safe view name to prevent SQL injection +MaterializedViewName = Literal[ + "mv_daily_meeting_stats", + "mv_speaker_stats", + "mv_entity_category_stats", + "mv_top_entities", + "mv_meeting_totals", + "mv_entity_totals", +] + +# Frozen set for runtime validation +VALID_VIEW_NAMES: Final[frozenset[str]] = frozenset(get_args(MaterializedViewName)) + +# Ordered list of all materialized views +ALL_MATERIALIZED_VIEWS: Final[tuple[MaterializedViewName, ...]] = ( + "mv_daily_meeting_stats", + "mv_speaker_stats", + "mv_entity_category_stats", + "mv_top_entities", + "mv_meeting_totals", + "mv_entity_totals", +) + + +class DailyStatsMVRow(Protocol): + meeting_date: date + meeting_count: int + total_duration_seconds: float + word_count: int + + +class MeetingTotalsMVRow(Protocol): + total_meetings: int + total_duration_seconds: float + total_segments: int + speaker_count: int + total_words: int + + +class SpeakerStatsMVRow(Protocol): + speaker_id: str + segment_count: int + total_time_seconds: float + meeting_count: int + avg_confidence: float + + +class EntityCategoryMVRow(Protocol): + category: str + entity_count: int + total_mentions: int + + +class TopEntityMVRow(Protocol): + text: str + category: str + mention_count: int + meeting_count: int + + +class EntityTotalsMVRow(Protocol): + total_entities: int + total_mentions: int + + +_QueryParams = dict[str, UUID | list[UUID] | date | int] + + +async def materialized_views_exist(session: AsyncSession) -> bool: + """Check if all required materialized views exist.""" + result = await session.execute( + text(""" + SELECT COUNT(*) FROM pg_matviews + WHERE schemaname = 'noteflow' + AND matviewname = ANY(:view_names) + """).bindparams(view_names=list(ALL_MATERIALIZED_VIEWS)) + ) + count = result.scalar() + return count is not None and count == len(ALL_MATERIALIZED_VIEWS) + + +async def refresh_materialized_view(session: AsyncSession, view_name: MaterializedViewName) -> None: + """Refresh a single materialized view. + + Note: Does not use CONCURRENTLY to avoid requiring autocommit mode. + This means the view is locked during refresh, but simplifies transaction handling. + """ + if view_name not in VALID_VIEW_NAMES: + msg = f"Invalid materialized view name: {view_name}" + raise ValueError(msg) + await session.execute(text(f"REFRESH MATERIALIZED VIEW noteflow.{view_name}")) + + +async def refresh_all_materialized_views(session: AsyncSession) -> None: + """Refresh all materialized views in order.""" + for view in ALL_MATERIALIZED_VIEWS: + await refresh_materialized_view(session, view) + + +def _build_daily_params( + workspace_id: UUID, + project_ids: list[UUID] | None, + start_time: datetime | None, + end_time: datetime | None, +) -> tuple[list[str], _QueryParams]: + conditions: list[str] = ["workspace_id = :workspace_id"] + params: _QueryParams = {"workspace_id": workspace_id} + + if project_ids: + conditions.append(f"project_id = ANY(:{PROJECT_IDS})") + params[PROJECT_IDS] = project_ids + if start_time: + conditions.append("meeting_date >= :start_date") + params["start_date"] = start_time.date() + if end_time: + conditions.append("meeting_date < :end_date") + params["end_date"] = end_time.date() + + return conditions, params + + +def _build_workspace_params( + workspace_id: UUID, + project_ids: list[UUID] | None, +) -> tuple[list[str], _QueryParams]: + conditions: list[str] = ["workspace_id = :workspace_id"] + params: _QueryParams = {"workspace_id": workspace_id} + + if project_ids: + conditions.append(f"project_id = ANY(:{PROJECT_IDS})") + params[PROJECT_IDS] = project_ids + + return conditions, params + + +async def fetch_daily_stats_mv( + session: AsyncSession, + params: AnalyticsQueryParams, +) -> list[DailyStatsMVRow]: + conditions, query_params = _build_daily_params( + params.workspace_id, params.project_ids, params.start_time, params.end_time + ) + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + meeting_date, + SUM(meeting_count)::integer as meeting_count, + SUM(total_duration_seconds)::float as total_duration_seconds, + SUM(word_count)::integer as word_count + FROM noteflow.mv_daily_meeting_stats + WHERE {where_clause} + GROUP BY meeting_date + ORDER BY meeting_date + """).bindparams(**query_params) + + result = await session.execute(query) + return typing_cast(list[DailyStatsMVRow], result.all()) + + +async def fetch_meeting_totals_mv( + session: AsyncSession, + params: AnalyticsQueryParams, +) -> MeetingTotalsMVRow | None: + if params.start_time or params.end_time: + return None + + conditions, query_params = _build_workspace_params(params.workspace_id, params.project_ids) + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + SUM(total_meetings)::integer as total_meetings, + SUM(total_duration_seconds)::float as total_duration_seconds, + SUM(total_segments)::integer as total_segments, + SUM(speaker_count)::integer as speaker_count, + SUM(total_words)::integer as total_words + FROM noteflow.mv_meeting_totals + WHERE {where_clause} + """).bindparams(**query_params) + + result = await session.execute(query) + row = result.one_or_none() + if row is None or row.total_meetings is None: + return None + return typing_cast(MeetingTotalsMVRow, row) + + +async def fetch_speaker_stats_mv( + session: AsyncSession, + params: AnalyticsQueryParams, +) -> list[SpeakerStatsMVRow] | None: + if params.start_time or params.end_time: + return None + + conditions, query_params = _build_workspace_params(params.workspace_id, params.project_ids) + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + speaker_id, + SUM(segment_count)::integer as segment_count, + SUM(total_time_seconds)::float as total_time_seconds, + SUM(meeting_count)::integer as meeting_count, + AVG(avg_confidence)::float as avg_confidence + FROM noteflow.mv_speaker_stats + WHERE {where_clause} + GROUP BY speaker_id + ORDER BY SUM(total_time_seconds) DESC + """).bindparams(**query_params) + + result = await session.execute(query) + return typing_cast(list[SpeakerStatsMVRow], result.all()) + + +async def fetch_entity_category_mv( + session: AsyncSession, + params: AnalyticsQueryParams, +) -> list[EntityCategoryMVRow] | None: + if params.start_time or params.end_time: + return None + + conditions, query_params = _build_workspace_params(params.workspace_id, params.project_ids) + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + category, + SUM(entity_count)::integer as entity_count, + SUM(total_mentions)::integer as total_mentions + FROM noteflow.mv_entity_category_stats + WHERE {where_clause} + GROUP BY category + ORDER BY SUM(entity_count) DESC + """).bindparams(**query_params) + + result = await session.execute(query) + return typing_cast(list[EntityCategoryMVRow], result.all()) + + +async def fetch_top_entities_mv( + session: AsyncSession, + params: AnalyticsQueryParams, + *, + limit: int = 20, +) -> list[TopEntityMVRow] | None: + if params.start_time or params.end_time: + return None + + conditions, query_params = _build_workspace_params(params.workspace_id, params.project_ids) + query_params["limit"] = limit + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + text, + category, + SUM(mention_count)::integer as mention_count, + SUM(meeting_count)::integer as meeting_count + FROM noteflow.mv_top_entities + WHERE {where_clause} + GROUP BY text, category + ORDER BY SUM(mention_count) DESC + LIMIT :limit + """).bindparams(**query_params) + + result = await session.execute(query) + return typing_cast(list[TopEntityMVRow], result.all()) + + +async def fetch_entity_totals_mv( + session: AsyncSession, + params: AnalyticsQueryParams, +) -> EntityTotalsMVRow | None: + if params.start_time or params.end_time: + return None + + conditions, query_params = _build_workspace_params(params.workspace_id, params.project_ids) + where_clause = " AND ".join(conditions) + query = text(f""" + SELECT + SUM(total_entities)::integer as total_entities, + SUM(total_mentions)::integer as total_mentions + FROM noteflow.mv_entity_totals + WHERE {where_clause} + """).bindparams(**query_params) + + result = await session.execute(query) + row = result.one_or_none() + if row is None or row.total_entities is None: + return None + return typing_cast(EntityTotalsMVRow, row) diff --git a/src/noteflow/infrastructure/persistence/repositories/analytics_repo.py b/src/noteflow/infrastructure/persistence/repositories/analytics_repo.py index 9146966..16486bf 100644 --- a/src/noteflow/infrastructure/persistence/repositories/analytics_repo.py +++ b/src/noteflow/infrastructure/persistence/repositories/analytics_repo.py @@ -7,30 +7,89 @@ from uuid import UUID from noteflow.domain.entities.analytics import ( AnalyticsOverview, - DailyMeetingStats, + EntityAnalytics, SpeakerStat, ) +from noteflow.infrastructure.logging import get_logger from noteflow.infrastructure.persistence.repositories._base import BaseRepository from noteflow.infrastructure.persistence.repositories._analytics_queries import ( - DailyStatsRow, - SpeakerStatsRow, + category_stats_stmt, daily_stats_stmt, + entity_conditions, + entity_totals_stmt, + fetch_category_rows, fetch_daily_rows, + fetch_entity_totals_row, fetch_segment_totals_row, fetch_speaker_rows, + fetch_top_entity_rows, fetch_totals_row, fetch_word_counts, meeting_conditions, segment_conditions, segment_totals_stmt, - speaker_conditions, speaker_stats_stmt, + top_entities_stmt, totals_stmt, word_count_stmt, ) +from noteflow.domain.value_objects import AnalyticsQueryParams, EntityAnalyticsQueryParams +from noteflow.infrastructure.persistence.repositories._analytics_converters import ( + build_daily_stats, + category_stat_from_mv, + category_stat_from_row, + daily_stat_from_mv, + speaker_stat_from_mv, + speaker_stat_from_row, + top_entity_from_mv, + top_entity_from_row, +) +from noteflow.infrastructure.persistence.repositories._materialized_view_queries import ( + fetch_daily_stats_mv, + fetch_entity_category_mv, + fetch_entity_totals_mv, + fetch_meeting_totals_mv, + fetch_speaker_stats_mv, + fetch_top_entities_mv, + materialized_views_exist, + refresh_all_materialized_views, + refresh_materialized_view, +) + +logger = get_logger(__name__) class SqlAlchemyAnalyticsRepository(BaseRepository): + _mv_available: bool | None = None + + @classmethod + def reset_mv_cache(cls) -> None: + """Reset the materialized view availability cache. + + Call this after migrations that create or drop materialized views. + """ + cls._mv_available = None + + async def materialized_views_available(self) -> bool: + if SqlAlchemyAnalyticsRepository._mv_available is None: + SqlAlchemyAnalyticsRepository._mv_available = await materialized_views_exist( + self._session + ) + return SqlAlchemyAnalyticsRepository._mv_available + + async def refresh_all_views(self) -> None: + await refresh_all_materialized_views(self._session) + + async def refresh_meeting_views(self) -> None: + await refresh_materialized_view(self._session, "mv_daily_meeting_stats") + await refresh_materialized_view(self._session, "mv_meeting_totals") + await refresh_materialized_view(self._session, "mv_speaker_stats") + + async def refresh_entity_views(self) -> None: + await refresh_materialized_view(self._session, "mv_entity_category_stats") + await refresh_materialized_view(self._session, "mv_top_entities") + await refresh_materialized_view(self._session, "mv_entity_totals") + async def get_overview( self, workspace_id: UUID, @@ -52,7 +111,7 @@ class SqlAlchemyAnalyticsRepository(BaseRepository): self._session, word_count_stmt(meeting_filters), ) - daily_stats = _build_daily_stats(daily_rows, word_counts_by_date) + daily_stats = build_daily_stats(daily_rows, word_counts_by_date) totals_row = await fetch_totals_row( self._session, @@ -86,7 +145,7 @@ class SqlAlchemyAnalyticsRepository(BaseRepository): start_time: datetime | None, end_time: datetime | None, ) -> list[SpeakerStat]: - speaker_filters = speaker_conditions( + speaker_filters = segment_conditions( workspace_id, project_ids, start_time, @@ -96,33 +155,142 @@ class SqlAlchemyAnalyticsRepository(BaseRepository): self._session, speaker_stats_stmt(speaker_filters), ) - return [_speaker_stat_from_row(row) for row in rows] + return [speaker_stat_from_row(row) for row in rows] - -def _build_daily_stats( - daily_rows: list[DailyStatsRow], - word_counts_by_date: dict[str, int], -) -> list[DailyMeetingStats]: - daily_stats: list[DailyMeetingStats] = [] - for row in daily_rows: - date_str = str(row.date) - daily_stats.append( - DailyMeetingStats( - date=date_str, - meetings=int(row.meetings), - total_duration=float(row.total_duration), - word_count=word_counts_by_date.get(date_str, 0), - ) + async def get_entity_analytics( + self, + params: EntityAnalyticsQueryParams, + ) -> EntityAnalytics: + filters = entity_conditions( + params.workspace_id, params.project_ids, params.start_time, params.end_time ) - return daily_stats + category_rows = await fetch_category_rows( + self._session, + category_stats_stmt(filters), + ) + top_rows = await fetch_top_entity_rows( + self._session, + top_entities_stmt(filters, limit=params.top_limit), + ) + totals_row = await fetch_entity_totals_row( + self._session, + entity_totals_stmt(filters), + ) -def _speaker_stat_from_row(row: SpeakerStatsRow) -> SpeakerStat: - return SpeakerStat( - speaker_id=str(row.speaker_id), - display_name=str(row.speaker_id), - total_time=float(row.total_time), - segment_count=int(row.segment_count), - meeting_count=int(row.meeting_count), - avg_confidence=float(row.avg_confidence), - ) + return EntityAnalytics( + by_category=[category_stat_from_row(r) for r in category_rows], + top_entities=[top_entity_from_row(r) for r in top_rows], + total_entities=int(totals_row.total_entities), + total_mentions=int(totals_row.total_mentions), + ) + + async def get_overview_fast( + self, + workspace_id: UUID, + project_ids: list[UUID] | None, + start_time: datetime | None, + end_time: datetime | None, + ) -> AnalyticsOverview: + if await self.materialized_views_available(): + params = AnalyticsQueryParams( + workspace_id=workspace_id, + project_ids=project_ids, + start_time=start_time, + end_time=end_time, + ) + mv_result = await self._get_overview_from_mv(params) + if mv_result is not None: + logger.debug("analytics_mv_hit", method="get_overview") + return mv_result + logger.debug("analytics_mv_fallback", method="get_overview") + + return await self.get_overview(workspace_id, project_ids, start_time, end_time) + + async def _get_overview_from_mv( + self, + params: AnalyticsQueryParams, + ) -> AnalyticsOverview | None: + daily_rows = await fetch_daily_stats_mv(self._session, params) + daily_stats = [daily_stat_from_mv(r) for r in daily_rows] + + totals = await fetch_meeting_totals_mv(self._session, params) + if totals is None: + return None + + return AnalyticsOverview( + daily=daily_stats, + total_meetings=int(totals.total_meetings), + total_duration=float(totals.total_duration_seconds), + total_words=int(totals.total_words), + total_segments=int(totals.total_segments), + speaker_count=int(totals.speaker_count), + ) + + async def get_speaker_stats_fast( + self, + workspace_id: UUID, + project_ids: list[UUID] | None, + start_time: datetime | None, + end_time: datetime | None, + ) -> list[SpeakerStat]: + if await self.materialized_views_available(): + params = AnalyticsQueryParams( + workspace_id=workspace_id, + project_ids=project_ids, + start_time=start_time, + end_time=end_time, + ) + mv_result = await fetch_speaker_stats_mv(self._session, params) + if mv_result is not None: + logger.debug("analytics_mv_hit", method="get_speaker_stats") + return [speaker_stat_from_mv(r) for r in mv_result] + logger.debug("analytics_mv_fallback", method="get_speaker_stats") + + return await self.get_speaker_stats(workspace_id, project_ids, start_time, end_time) + + async def get_entity_analytics_fast( + self, + params: EntityAnalyticsQueryParams, + ) -> EntityAnalytics: + if await self.materialized_views_available(): + base_params = AnalyticsQueryParams( + workspace_id=params.workspace_id, + project_ids=params.project_ids, + start_time=params.start_time, + end_time=params.end_time, + ) + mv_result = await self._get_entity_analytics_from_mv( + base_params, top_limit=params.top_limit + ) + if mv_result is not None: + logger.debug("analytics_mv_hit", method="get_entity_analytics") + return mv_result + logger.debug("analytics_mv_fallback", method="get_entity_analytics") + + return await self.get_entity_analytics(params) + + async def _get_entity_analytics_from_mv( + self, + params: AnalyticsQueryParams, + *, + top_limit: int, + ) -> EntityAnalytics | None: + category_rows = await fetch_entity_category_mv(self._session, params) + if category_rows is None: + return None + + top_rows = await fetch_top_entities_mv(self._session, params, limit=top_limit) + if top_rows is None: + return None + + totals = await fetch_entity_totals_mv(self._session, params) + if totals is None: + return None + + return EntityAnalytics( + by_category=[category_stat_from_mv(r) for r in category_rows], + top_entities=[top_entity_from_mv(r) for r in top_rows], + total_entities=int(totals.total_entities), + total_mentions=int(totals.total_mentions), + ) diff --git a/tests/application/test_analytics_service.py b/tests/application/test_analytics_service.py index 2c48017..59e267f 100644 --- a/tests/application/test_analytics_service.py +++ b/tests/application/test_analytics_service.py @@ -110,14 +110,16 @@ class TestAnalyticsServiceGetOverview: ) -> None: """Test successful overview retrieval from repository.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) result = await analytics_service.get_overview(WORKSPACE_ID) assert result.total_meetings == 5, "Total meetings should match" assert result.total_words == EXPECTED_TOTAL_WORDS, "Total words should match" assert len(result.daily) == 2, "Should have 2 daily entries" - mock_uow.analytics.get_overview.assert_called_once() + mock_uow.analytics.get_overview_fast.assert_called_once() async def test_get_overview_with_project_filter( self, @@ -127,14 +129,16 @@ class TestAnalyticsServiceGetOverview: ) -> None: """Test overview retrieval with project IDs filter.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) await analytics_service.get_overview( WORKSPACE_ID, project_ids=[PROJECT_ID_1, PROJECT_ID_2], ) - mock_uow.analytics.get_overview.assert_called_once_with( + mock_uow.analytics.get_overview_fast.assert_called_once_with( workspace_id=WORKSPACE_ID, project_ids=[PROJECT_ID_1, PROJECT_ID_2], start_time=None, @@ -149,7 +153,9 @@ class TestAnalyticsServiceGetOverview: ) -> None: """Test overview retrieval with time range filter.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) start = datetime(2026, 1, 1, tzinfo=UTC) end = datetime(2026, 1, 31, tzinfo=UTC) @@ -160,7 +166,7 @@ class TestAnalyticsServiceGetOverview: end_time=end, ) - mock_uow.analytics.get_overview.assert_called_once_with( + mock_uow.analytics.get_overview_fast.assert_called_once_with( workspace_id=WORKSPACE_ID, project_ids=None, start_time=start, @@ -179,14 +185,16 @@ class TestAnalyticsServiceGetSpeakerStats: ) -> None: """Test successful speaker stats retrieval from repository.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_speaker_stats = AsyncMock(return_value=sample_speaker_stats) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_speaker_stats_fast = AsyncMock(return_value=sample_speaker_stats) result = await analytics_service.get_speaker_stats(WORKSPACE_ID) assert len(result) == 2, "Should return 2 speakers" assert result[0].display_name == "Alice", "First speaker should be Alice" assert result[1].display_name == "Bob", "Second speaker should be Bob" - mock_uow.analytics.get_speaker_stats.assert_called_once() + mock_uow.analytics.get_speaker_stats_fast.assert_called_once() async def test_get_speaker_stats_with_filters( self, @@ -196,7 +204,9 @@ class TestAnalyticsServiceGetSpeakerStats: ) -> None: """Test speaker stats with project and time filters.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_speaker_stats = AsyncMock(return_value=sample_speaker_stats) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_speaker_stats_fast = AsyncMock(return_value=sample_speaker_stats) start = datetime(2026, 1, 15, tzinfo=UTC) end = datetime(2026, 1, 22, tzinfo=UTC) @@ -208,7 +218,7 @@ class TestAnalyticsServiceGetSpeakerStats: end_time=end, ) - mock_uow.analytics.get_speaker_stats.assert_called_once_with( + mock_uow.analytics.get_speaker_stats_fast.assert_called_once_with( workspace_id=WORKSPACE_ID, project_ids=[PROJECT_ID_1], start_time=start, @@ -227,7 +237,9 @@ class TestAnalyticsServiceCaching: ) -> None: """Test that second call returns cached data without DB query.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) # First call - cache miss await analytics_service.get_overview(WORKSPACE_ID) @@ -235,7 +247,7 @@ class TestAnalyticsServiceCaching: result = await analytics_service.get_overview(WORKSPACE_ID) assert result.total_meetings == 5, "Should return cached data" - assert mock_uow.analytics.get_overview.call_count == 1, "Should only call DB once" + assert mock_uow.analytics.get_overview_fast.call_count == 1, "Should only call DB once" async def test_cache_expires_after_ttl( self, @@ -246,7 +258,9 @@ class TestAnalyticsServiceCaching: # Create service with 0 second TTL for immediate expiry testing service = AnalyticsService(analytics_uow_factory, cache_ttl_seconds=0) mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) # First call await service.get_overview(WORKSPACE_ID) @@ -259,7 +273,9 @@ class TestAnalyticsServiceCaching: # Second call - cache should be expired await service.get_overview(WORKSPACE_ID) - assert mock_uow.analytics.get_overview.call_count == 2, "Should query DB twice after TTL" + assert mock_uow.analytics.get_overview_fast.call_count == 2, ( + "Should query DB twice after TTL" + ) async def test_different_filters_use_different_cache_keys( self, @@ -269,14 +285,16 @@ class TestAnalyticsServiceCaching: ) -> None: """Test that different filter parameters use separate cache entries.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) # Call with no filter await analytics_service.get_overview(WORKSPACE_ID) # Call with project filter - should be cache miss await analytics_service.get_overview(WORKSPACE_ID, project_ids=[PROJECT_ID_1]) - assert mock_uow.analytics.get_overview.call_count == 2, ( + assert mock_uow.analytics.get_overview_fast.call_count == 2, ( "Different filters should query DB separately" ) @@ -292,7 +310,9 @@ class TestAnalyticsServiceCacheInvalidation: ) -> None: """Test invalidating cache for specific workspace.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) # Populate cache await analytics_service.get_overview(WORKSPACE_ID) @@ -303,7 +323,9 @@ class TestAnalyticsServiceCacheInvalidation: # Next call should hit DB await analytics_service.get_overview(WORKSPACE_ID) - assert mock_uow.analytics.get_overview.call_count == 2, "Should query DB after invalidation" + assert mock_uow.analytics.get_overview_fast.call_count == 2, ( + "Should query DB after invalidation" + ) async def test_invalidate_all_caches( self, @@ -314,8 +336,10 @@ class TestAnalyticsServiceCacheInvalidation: ) -> None: """Test invalidating all caches when no workspace specified.""" mock_uow = analytics_uow_factory.return_value - mock_uow.analytics.get_overview = AsyncMock(return_value=sample_overview) - mock_uow.analytics.get_speaker_stats = AsyncMock(return_value=sample_speaker_stats) + mock_uow.__aenter__ = AsyncMock(return_value=mock_uow) + mock_uow.__aexit__ = AsyncMock(return_value=None) + mock_uow.analytics.get_overview_fast = AsyncMock(return_value=sample_overview) + mock_uow.analytics.get_speaker_stats_fast = AsyncMock(return_value=sample_speaker_stats) # Populate both caches await analytics_service.get_overview(WORKSPACE_ID) @@ -328,9 +352,9 @@ class TestAnalyticsServiceCacheInvalidation: await analytics_service.get_overview(WORKSPACE_ID) await analytics_service.get_speaker_stats(WORKSPACE_ID) - assert mock_uow.analytics.get_overview.call_count == 2, ( + assert mock_uow.analytics.get_overview_fast.call_count == 2, ( "Overview should query DB after invalidation" ) - assert mock_uow.analytics.get_speaker_stats.call_count == 2, ( + assert mock_uow.analytics.get_speaker_stats_fast.call_count == 2, ( "Speaker stats should query DB after invalidation" ) diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 0000000..f135283 --- /dev/null +++ b/tests/cli/__init__.py @@ -0,0 +1 @@ +"""CLI tests package.""" diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py new file mode 100644 index 0000000..5f2d0ea --- /dev/null +++ b/tests/cli/conftest.py @@ -0,0 +1,201 @@ +"""Fixtures for CLI tests.""" + +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING +from unittest.mock import MagicMock, patch + +import pytest + +if TYPE_CHECKING: + from collections.abc import Generator + + +@pytest.fixture +def mock_cli_console() -> Generator[MagicMock, None, None]: + """Mock the CLI console for capturing output.""" + with patch("noteflow.cli.__main__.console") as mock_console: + yield mock_console + + +@pytest.fixture +def mock_retention_main() -> Generator[MagicMock, None, None]: + """Mock the retention.main function.""" + with patch("noteflow.cli.retention.main") as mock_main: + yield mock_main + + +@pytest.fixture +def mock_models_main() -> Generator[MagicMock, None, None]: + """Mock the models.main function.""" + with patch("noteflow.cli.models.main") as mock_main: + yield mock_main + + +@pytest.fixture +def argv_no_args() -> Generator[None, None, None]: + """Set sys.argv to simulate no arguments.""" + with patch.object(sys, "argv", ["noteflow.cli"]): + yield + + +@pytest.fixture +def argv_unknown_command() -> Generator[None, None, None]: + """Set sys.argv to simulate unknown command.""" + with patch.object(sys, "argv", ["noteflow.cli", "unknown"]): + yield + + +@pytest.fixture +def argv_retention_status() -> Generator[None, None, None]: + """Set sys.argv to simulate retention status command.""" + with patch.object(sys, "argv", ["noteflow.cli", "retention", "status"]): + yield + + +@pytest.fixture +def argv_models_list() -> Generator[None, None, None]: + """Set sys.argv to simulate models list command.""" + with patch.object(sys, "argv", ["noteflow.cli", "models", "list"]): + yield + + +@pytest.fixture +def mock_retention_main_error() -> Generator[MagicMock, None, None]: + """Mock retention.main that raises RuntimeError.""" + with patch("noteflow.cli.retention.main") as mock_main: + mock_main.side_effect = RuntimeError("Test error") + yield mock_main + + +# ============================================================================= +# Retention CLI fixtures +# ============================================================================= + + +@pytest.fixture +def mock_retention_console() -> Generator[MagicMock, None, None]: + """Mock the retention CLI console.""" + with patch("noteflow.cli.retention.console") as mock_console: + yield mock_console + + +@pytest.fixture +def mock_retention_settings() -> Generator[MagicMock, None, None]: + """Mock get_settings for retention tests.""" + with patch("noteflow.cli.retention.get_settings") as mock_get: + settings = MagicMock() + settings.retention_enabled = True + settings.retention_days = 30 + settings.retention_check_interval_hours = 24 + mock_get.return_value = settings + yield settings + + +@pytest.fixture +def mock_retention_settings_disabled() -> Generator[MagicMock, None, None]: + """Mock get_settings with retention disabled.""" + with patch("noteflow.cli.retention.get_settings") as mock_get: + settings = MagicMock() + settings.retention_enabled = False + settings.retention_days = 30 + settings.retention_check_interval_hours = 24 + mock_get.return_value = settings + yield settings + + +@pytest.fixture +def mock_retention_uow_factory() -> Generator[MagicMock, None, None]: + """Mock create_uow_factory for retention tests.""" + with patch("noteflow.cli.retention.create_uow_factory") as mock_factory: + mock_factory.return_value = MagicMock() + yield mock_factory + + +@pytest.fixture +def mock_retention_service() -> Generator[MagicMock, None, None]: + """Mock RetentionService for retention tests.""" + from datetime import UTC, datetime + + with patch("noteflow.cli.retention.RetentionService") as mock_cls: + service = MagicMock() + service.retention_days = 30 + service.cutoff_date = datetime(2024, 1, 1, tzinfo=UTC) + mock_cls.return_value = service + yield service + + +@pytest.fixture +def argv_retention_cleanup() -> Generator[None, None, None]: + """Set sys.argv for retention cleanup command.""" + with patch.object(sys, "argv", ["noteflow.cli.retention", "cleanup"]): + yield + + +@pytest.fixture +def argv_retention_cleanup_dry_run() -> Generator[None, None, None]: + """Set sys.argv for retention cleanup --dry-run command.""" + with patch.object(sys, "argv", ["noteflow.cli.retention", "cleanup", "--dry-run"]): + yield + + +@pytest.fixture +def argv_retention_status_cmd() -> Generator[None, None, None]: + """Set sys.argv for retention status command.""" + with patch.object(sys, "argv", ["noteflow.cli.retention", "status"]): + yield + + +@pytest.fixture +def argv_retention_no_command() -> Generator[None, None, None]: + """Set sys.argv for retention with no subcommand.""" + with patch.object(sys, "argv", ["noteflow.cli.retention"]): + yield + + +# ============================================================================= +# Models CLI fixtures +# ============================================================================= + + +@pytest.fixture +def mock_models_console() -> Generator[MagicMock, None, None]: + """Mock the models CLI console.""" + with patch("noteflow.cli.models._status.console") as mock_console: + yield mock_console + + +@pytest.fixture +def argv_models_download() -> Generator[None, None, None]: + """Set sys.argv for models download command.""" + with patch.object(sys, "argv", ["noteflow.cli.models", "download"]): + yield + + +@pytest.fixture +def argv_models_download_specific() -> Generator[None, None, None]: + """Set sys.argv for models download with specific model.""" + with patch.object(sys, "argv", ["noteflow.cli.models", "download", "--model", "spacy-en"]): + yield + + +@pytest.fixture +def argv_models_list_cmd() -> Generator[None, None, None]: + """Set sys.argv for models list command.""" + with patch.object(sys, "argv", ["noteflow.cli.models", "list"]): + yield + + +@pytest.fixture +def argv_models_status() -> Generator[None, None, None]: + """Set sys.argv for models status command.""" + with patch.object(sys, "argv", ["noteflow.cli.models", "status"]): + yield + + +@pytest.fixture +def argv_models_no_command() -> Generator[None, None, None]: + """Set sys.argv for models with no subcommand.""" + with patch.object(sys, "argv", ["noteflow.cli.models"]): + yield diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py new file mode 100644 index 0000000..887ca6b --- /dev/null +++ b/tests/cli/test_main.py @@ -0,0 +1,110 @@ +"""Tests for CLI main entry point.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + +import noteflow.cli.__main__ as cli_main +from noteflow.cli.__main__ import main + +dispatch_command = getattr(cli_main, "_dispatch_command") +run_models_command = getattr(cli_main, "_run_models_command") +run_retention_command = getattr(cli_main, "_run_retention_command") +show_help = getattr(cli_main, "_show_help") + + +class TestShowHelp: + def test_show_help_calls_console_print(self, mock_cli_console: MagicMock) -> None: + show_help() + assert mock_cli_console.print.call_count > 0, "Help should print multiple lines" + + +class TestDispatchCommand: + def test_dispatch_retention_command_returns_true(self, mock_retention_main: MagicMock) -> None: + assert mock_retention_main is not None, "Retention handler fixture should be provided" + result = dispatch_command("retention", ["status"]) + assert result is True, "Known command should return True" + + def test_dispatch_retention_command_calls_handler(self, mock_retention_main: MagicMock) -> None: + dispatch_command("retention", ["status"]) + mock_retention_main.assert_called_once() + + def test_dispatch_models_command_returns_true(self, mock_models_main: MagicMock) -> None: + assert mock_models_main is not None, "Models handler fixture should be provided" + result = dispatch_command("models", ["list"]) + assert result is True, "Known command should return True" + + def test_dispatch_models_command_calls_handler(self, mock_models_main: MagicMock) -> None: + dispatch_command("models", ["list"]) + mock_models_main.assert_called_once() + + def test_dispatch_unknown_command_returns_false(self) -> None: + result = dispatch_command("unknown_command", []) + assert result is False, "Unknown command should return False" + + def test_dispatch_empty_command_returns_false(self) -> None: + result = dispatch_command("", []) + assert result is False, "Empty command should return False" + + def test_dispatch_command_propagates_exception( + self, mock_retention_main_error: MagicMock + ) -> None: + assert mock_retention_main_error is not None, "Retention error fixture should be provided" + with pytest.raises(RuntimeError, match="Test error"): + dispatch_command("retention", []) + + +class TestRunRetentionCommand: + def test_run_retention_calls_main(self, mock_retention_main: MagicMock) -> None: + run_retention_command("retention") + mock_retention_main.assert_called_once() + + +class TestRunModelsCommand: + def test_run_models_calls_main(self, mock_models_main: MagicMock) -> None: + run_models_command("models") + mock_models_main.assert_called_once() + + +class TestMain: + def test_main_no_args_exits_with_code_1( + self, argv_no_args: None, mock_cli_console: MagicMock + ) -> None: + assert argv_no_args is None, "argv_no_args fixture should provide None" + assert mock_cli_console is not None, "Console fixture should be provided" + with pytest.raises(SystemExit, match="1") as exc_info: + main() + assert exc_info.value.code == 1, "Should exit with code 1" + + def test_main_unknown_command_exits_with_code_1( + self, argv_unknown_command: None, mock_cli_console: MagicMock + ) -> None: + assert argv_unknown_command is None, "argv_unknown_command fixture should provide None" + assert mock_cli_console is not None, "Console fixture should be provided" + with pytest.raises(SystemExit, match="1") as exc_info: + main() + assert exc_info.value.code == 1, "Should exit with code 1" + + def test_main_unknown_command_prints_error( + self, argv_unknown_command: None, mock_cli_console: MagicMock + ) -> None: + assert argv_unknown_command is None, "argv_unknown_command fixture should provide None" + with pytest.raises(SystemExit, match="1"): + main() + assert mock_cli_console.print.call_count > 0, "Should print error" + + def test_main_retention_command_dispatches( + self, argv_retention_status: None, mock_retention_main: MagicMock + ) -> None: + assert argv_retention_status is None, "argv_retention_status fixture should provide None" + main() + mock_retention_main.assert_called_once() + + def test_main_models_command_dispatches( + self, argv_models_list: None, mock_models_main: MagicMock + ) -> None: + assert argv_models_list is None, "argv_models_list fixture should provide None" + main() + mock_models_main.assert_called_once() diff --git a/tests/cli/test_models.py b/tests/cli/test_models.py new file mode 100644 index 0000000..1bad2d9 --- /dev/null +++ b/tests/cli/test_models.py @@ -0,0 +1,200 @@ +"""Tests for CLI models command.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + +from noteflow.cli.models import execute_command, main, run_download +from noteflow.cli.models._download import ( + check_model_installed, + download_model, + resolve_models_to_download, +) +from noteflow.cli.models._parser import create_argument_parser +from noteflow.cli.models._registry import AVAILABLE_MODELS, DEFAULT_MODEL +from noteflow.cli.models._status import list_models, print_download_report, show_status +from noteflow.cli.models._types import ( + DownloadReport, + DownloadResult, + ModelInfo, + ModelStatus, +) + + +class TestModelInfo: + def test_model_info_name(self) -> None: + info = ModelInfo( + name="test-model", + description="Test description", + feature="test", + install_command=["python", "-m", "test"], + check_import="test_module", + ) + assert info.name == "test-model", "Name should match" + + +class TestDownloadReport: + def test_download_report_empty_success_count(self) -> None: + report = DownloadReport() + assert report.success_count == 0, "Empty report has 0 successes" + + def test_download_report_empty_failure_count(self) -> None: + report = DownloadReport() + assert report.failure_count == 0, "Empty report has 0 failures" + + def test_download_report_counts_successes(self) -> None: + report = DownloadReport( + results=[ + DownloadResult(model_name="a", success=True), + DownloadResult(model_name="b", success=True), + ] + ) + assert report.success_count == 2, "Should count successes" + + def test_download_report_counts_failures(self) -> None: + report = DownloadReport( + results=[ + DownloadResult(model_name="a", success=False, error="e1"), + DownloadResult(model_name="b", success=False, error="e2"), + ] + ) + assert report.failure_count == 2, "Should count failures" + + +class TestCheckModelInstalled: + def test_check_model_installed_found(self) -> None: + info = ModelInfo( + name="sys", + description="System module", + feature="test", + install_command=[], + check_import="sys", + ) + status = check_model_installed(info) + assert status.installed is True, "sys module should be importable" + + def test_check_model_not_installed(self) -> None: + info = ModelInfo( + name="nonexistent", + description="Nonexistent", + feature="test", + install_command=[], + check_import="nonexistent_module_xyz_123", + ) + status = check_model_installed(info) + assert status.installed is False, "Nonexistent module not installed" + + +class TestDownloadModel: + def test_download_model_success(self) -> None: + info = ModelInfo( + name="test", + description="Test", + feature="test", + install_command=["echo", "success"], + check_import="test", + ) + result = download_model(info) + assert result.success is True, "Echo should succeed" + + def test_download_model_failure(self) -> None: + info = ModelInfo( + name="test", + description="Test", + feature="test", + install_command=["false"], + check_import="test", + ) + result = download_model(info) + assert result.success is False, "false command should fail" + + +class TestResolveModelsToDownload: + def test_resolve_none_returns_list(self) -> None: + models = resolve_models_to_download(None) + assert models is not None, "Should return models" + assert len(models) == 1, "Should return single model" + + def test_resolve_invalid_model_returns_none(self) -> None: + with patch("rich.console.Console"): + models = resolve_models_to_download("nonexistent_model_xyz") + assert models is None, "Invalid model should return None" + + +class TestCreateArgumentParser: + def test_parser_created(self) -> None: + parser = create_argument_parser() + assert parser is not None, "Parser should be created" + + def test_parser_download_command(self) -> None: + parser = create_argument_parser() + args = parser.parse_args(["download"]) + assert args.command == "download", "Should parse download command" + + +class TestRegistry: + def test_available_models_not_empty(self) -> None: + assert len(AVAILABLE_MODELS) > 0, "Should have available models" + + def test_default_model_exists(self) -> None: + assert DEFAULT_MODEL is not None, "Default model should exist" + + +class TestListModels: + def test_list_models_returns_0(self, mock_models_console: MagicMock) -> None: + assert mock_models_console is not None, "Models console fixture should be provided" + with patch("noteflow.cli.models._status.check_model_installed") as mock_check: + mock_check.return_value = ModelStatus(model=MagicMock(), installed=False, error=None) + result = list_models() + assert result == 0, "list_models should return 0" + + +class TestShowStatus: + def test_show_status_returns_zero(self, mock_models_console: MagicMock) -> None: + assert mock_models_console is not None, "Models console fixture should be provided" + with patch("noteflow.cli.models._status.check_model_installed") as mock_check: + mock_check.return_value = ModelStatus(model=MagicMock(), installed=False, error=None) + result = show_status() + assert result == 0, "show_status should return 0" + + +class TestPrintDownloadReport: + def test_print_download_report_calls_console(self, mock_models_console: MagicMock) -> None: + report = DownloadReport(results=[DownloadResult(model_name="a", success=True)]) + print_download_report(report) + assert mock_models_console.print.call_count > 0, "Should print report" + + +class TestRunDownload: + def test_run_download_resolve_fails(self) -> None: + with patch("noteflow.cli.models.resolve_models_to_download") as mock_resolve: + mock_resolve.return_value = None + result = run_download("invalid") + assert result == 1, "Failed resolve should return 1" + + +class TestExecuteCommand: + def test_execute_command_invalid_args(self) -> None: + result = execute_command("not namespace", "not parser") + assert result == 1, "Invalid args should return 1" + + +class TestModelsMain: + def test_main_no_command_exits_1(self, argv_models_no_command: None) -> None: + assert argv_models_no_command is None, "argv_models_no_command fixture should provide None" + with pytest.raises(SystemExit, match="1") as exc_info: + main() + assert exc_info.value.code == 1, "No command should exit 1" + + def test_main_list_command_exits_0( + self, argv_models_list_cmd: None, mock_models_console: MagicMock + ) -> None: + assert argv_models_list_cmd is None, "argv_models_list_cmd fixture should provide None" + assert mock_models_console is not None, "Models console fixture should be provided" + with patch("noteflow.cli.models._status.check_model_installed") as mock_check: + mock_check.return_value = ModelStatus(model=MagicMock(), installed=False, error=None) + with pytest.raises(SystemExit, match="0") as exc_info: + main() + assert exc_info.value.code == 0, "List should exit 0" diff --git a/tests/cli/test_retention.py b/tests/cli/test_retention.py new file mode 100644 index 0000000..1f76fa1 --- /dev/null +++ b/tests/cli/test_retention.py @@ -0,0 +1,180 @@ +"""Tests for CLI retention command.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from unittest.mock import AsyncMock, MagicMock + +import pytest + +import noteflow.cli.retention as retention_cli +from noteflow.cli.retention import main + +run_cleanup = getattr(retention_cli, "_run_cleanup") +show_status = getattr(retention_cli, "_show_status") + + +class TestRunCleanup: + @pytest.mark.asyncio + async def test_run_cleanup_disabled_without_dry_run_returns_1( + self, + mock_retention_settings_disabled: MagicMock, + mock_retention_uow_factory: MagicMock, + ) -> None: + assert mock_retention_settings_disabled is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + result = await run_cleanup(dry_run=False) + assert result == 1, "Should return 1 when retention disabled and not dry-run" + + @pytest.mark.asyncio + async def test_run_cleanup_dry_run_proceeds_when_disabled( + self, + mock_retention_settings_disabled: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert mock_retention_settings_disabled is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.run_cleanup = AsyncMock( + return_value=MagicMock(meetings_checked=5, meetings_deleted=0, errors=[]) + ) + result = await run_cleanup(dry_run=True) + assert result == 0, "Dry-run should succeed even when retention disabled" + + @pytest.mark.asyncio + async def test_run_cleanup_success_returns_0( + self, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.run_cleanup = AsyncMock( + return_value=MagicMock(meetings_checked=10, meetings_deleted=3, errors=[]) + ) + result = await run_cleanup(dry_run=False) + assert result == 0, "Should return 0 on successful cleanup" + + @pytest.mark.asyncio + async def test_run_cleanup_with_errors_returns_1( + self, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.run_cleanup = AsyncMock( + return_value=MagicMock( + meetings_checked=10, meetings_deleted=2, errors=["Error 1", "Error 2"] + ) + ) + result = await run_cleanup(dry_run=False) + assert result == 1, "Should return 1 when errors occurred" + + +class TestShowStatus: + @pytest.mark.asyncio + async def test_show_status_returns_0( + self, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.find_expired_meetings = AsyncMock(return_value=[]) + result = await show_status() + assert result == 0, "Status should always return 0" + + @pytest.mark.asyncio + async def test_show_status_with_expired_meetings( + self, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + meeting = MagicMock() + meeting.id = "test-id" + meeting.title = "Test Meeting" + meeting.ended_at = datetime(2024, 1, 1, tzinfo=UTC) + mock_retention_service.find_expired_meetings = AsyncMock(return_value=[meeting]) + result = await show_status() + assert result == 0, "Status should return 0 even with pending deletions" + + +class TestMain: + def test_main_no_command_exits_with_1(self, argv_retention_no_command: None) -> None: + assert argv_retention_no_command is None, "argv_retention_no_command fixture should provide None" + with pytest.raises(SystemExit, match="1") as exc_info: + main() + assert exc_info.value.code == 1, "Should exit with 1 when no command" + + def test_main_cleanup_command_calls_run_cleanup( + self, + argv_retention_cleanup: None, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert argv_retention_cleanup is None, "argv_retention_cleanup fixture should provide None" + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.run_cleanup = AsyncMock( + return_value=MagicMock(meetings_checked=0, meetings_deleted=0, errors=[]) + ) + with pytest.raises(SystemExit, match="0") as exc_info: + main() + assert exc_info.value.code == 0, "Cleanup should exit with 0 on success" + + def test_main_cleanup_dry_run_passes_flag( + self, + argv_retention_cleanup_dry_run: None, + mock_retention_settings_disabled: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert argv_retention_cleanup_dry_run is None, "argv_retention_cleanup_dry_run fixture should provide None" + assert mock_retention_settings_disabled is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.run_cleanup = AsyncMock( + return_value=MagicMock(meetings_checked=0, meetings_deleted=0, errors=[]) + ) + with pytest.raises(SystemExit, match="0") as exc_info: + main() + assert exc_info.value.code == 0, "Dry-run should succeed" + mock_retention_service.run_cleanup.assert_called_once_with(dry_run=True) + + def test_main_status_command_calls_show_status( + self, + argv_retention_status_cmd: None, + mock_retention_settings: MagicMock, + mock_retention_uow_factory: MagicMock, + mock_retention_service: MagicMock, + mock_retention_console: MagicMock, + ) -> None: + assert argv_retention_status_cmd is None, "argv_retention_status_cmd fixture should provide None" + assert mock_retention_settings is not None, "Retention settings fixture should be provided" + assert mock_retention_uow_factory is not None, "Retention UOW fixture should be provided" + assert mock_retention_console is not None, "Retention console fixture should be provided" + mock_retention_service.find_expired_meetings = AsyncMock(return_value=[]) + with pytest.raises(SystemExit, match="0") as exc_info: + main() + assert exc_info.value.code == 0, "Status should exit with 0" diff --git a/tests/infrastructure/test_platform.py b/tests/infrastructure/test_platform.py new file mode 100644 index 0000000..4d0f06d --- /dev/null +++ b/tests/infrastructure/test_platform.py @@ -0,0 +1,155 @@ +"""Tests for platform detection utilities.""" + +from __future__ import annotations + +import os +from unittest.mock import MagicMock, mock_open, patch + +import noteflow.infrastructure.platform as platform +from noteflow.infrastructure.platform import configure_pytorch_for_platform, has_avx2_support + +read_linux_cpuinfo = getattr(platform, "_read_linux_cpuinfo") +read_sysctl_features = getattr(platform, "_read_sysctl_features") + + +class TestReadLinuxCpuinfo: + def test_returns_none_when_file_not_exists(self) -> None: + with patch("os.path.exists", return_value=False): + result = read_linux_cpuinfo() + assert result is None, "Should return None when /proc/cpuinfo doesn't exist" + + def test_returns_content_when_file_exists(self) -> None: + cpuinfo_content = "processor : 0\nflags : avx avx2 sse\n" + with ( + patch("os.path.exists", return_value=True), + patch("builtins.open", mock_open(read_data=cpuinfo_content)), + ): + result = read_linux_cpuinfo() + assert result == cpuinfo_content, "Should return file content" + + def test_returns_none_on_oserror_linux(self) -> None: + with ( + patch("os.path.exists", return_value=True), + patch("builtins.open", side_effect=OSError("Permission denied")), + ): + result = read_linux_cpuinfo() + assert result is None, "Should return None on OSError" + + +class TestReadSysctlFeatures: + def test_returns_features_on_success(self) -> None: + mock_result = MagicMock() + mock_result.returncode = 0 + mock_result.stdout = "FPU VME AVX2 SSE" + with patch("subprocess.run", return_value=mock_result): + result = read_sysctl_features() + assert result == "FPU VME AVX2 SSE", "Should return sysctl output" + + def test_returns_none_on_nonzero_returncode(self) -> None: + mock_result = MagicMock() + mock_result.returncode = 1 + mock_result.stdout = "" + with patch("subprocess.run", return_value=mock_result): + result = read_sysctl_features() + assert result is None, "Should return None on non-zero return code" + + def test_returns_none_on_oserror_sysctl(self) -> None: + with patch("subprocess.run", side_effect=OSError("Command not found")): + result = read_sysctl_features() + assert result is None, "Should return None on OSError" + + +class TestHasAvx2Support: + def test_returns_true_when_linux_cpuinfo_has_avx2(self) -> None: + has_avx2_support.cache_clear() + cpuinfo = "processor : 0\nflags : fpu avx2 sse4_2\n" + with ( + patch( + "noteflow.infrastructure.platform._read_linux_cpuinfo", + return_value=cpuinfo, + ), + ): + result = has_avx2_support() + assert result is True, "Should detect avx2 in cpuinfo" + + def test_returns_false_when_linux_cpuinfo_no_avx2(self) -> None: + has_avx2_support.cache_clear() + cpuinfo = "processor : 0\nflags : fpu sse4_2\n" + with ( + patch( + "noteflow.infrastructure.platform._read_linux_cpuinfo", + return_value=cpuinfo, + ), + ): + result = has_avx2_support() + assert result is False, "Should not detect avx2 when missing" + + def test_falls_back_to_sysctl_when_no_cpuinfo(self) -> None: + has_avx2_support.cache_clear() + with ( + patch( + "noteflow.infrastructure.platform._read_linux_cpuinfo", + return_value=None, + ), + patch( + "noteflow.infrastructure.platform._read_sysctl_features", + return_value="FPU AVX2 SSE", + ), + ): + result = has_avx2_support() + assert result is True, "Should detect avx2 via sysctl fallback" + + def test_returns_false_when_both_sources_unavailable(self) -> None: + has_avx2_support.cache_clear() + with ( + patch( + "noteflow.infrastructure.platform._read_linux_cpuinfo", + return_value=None, + ), + patch( + "noteflow.infrastructure.platform._read_sysctl_features", + return_value=None, + ), + ): + result = has_avx2_support() + assert result is False, "Should return False when no source available" + + +class TestConfigurePytorchForPlatform: + def test_sets_nnpack_disabled_when_no_avx2(self) -> None: + has_avx2_support.cache_clear() + with ( + patch( + "noteflow.infrastructure.platform.has_avx2_support", + return_value=False, + ), + patch.dict(os.environ, {}, clear=False), + ): + os.environ.pop("PYTORCH_DISABLE_NNPACK", None) + configure_pytorch_for_platform() + assert os.environ.get("PYTORCH_DISABLE_NNPACK") == "1", "Should disable NNPACK" + + def test_does_not_override_existing_nnpack_setting(self) -> None: + has_avx2_support.cache_clear() + with ( + patch( + "noteflow.infrastructure.platform.has_avx2_support", + return_value=False, + ), + patch.dict(os.environ, {"PYTORCH_DISABLE_NNPACK": "0"}, clear=False), + ): + configure_pytorch_for_platform() + assert os.environ.get("PYTORCH_DISABLE_NNPACK") == "0", "Should not override" + + def test_does_not_set_nnpack_when_avx2_supported(self) -> None: + has_avx2_support.cache_clear() + with ( + patch( + "noteflow.infrastructure.platform.has_avx2_support", + return_value=True, + ), + patch.dict(os.environ, {}, clear=False), + ): + os.environ.pop("PYTORCH_DISABLE_NNPACK", None) + configure_pytorch_for_platform() + assert "PYTORCH_DISABLE_NNPACK" not in os.environ, "Should not set when avx2 supported" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 1acb0e3..3b74ba9 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -3,15 +3,19 @@ from __future__ import annotations from collections.abc import AsyncGenerator +from datetime import UTC, datetime from pathlib import Path from typing import TYPE_CHECKING, Final +from uuid import UUID, uuid4 import numpy as np import pytest from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from noteflow.domain.entities import Meeting, Segment +from noteflow.domain.entities.task import Task, TaskStatus from noteflow.domain.value_objects import MeetingId +from noteflow.infrastructure.persistence.repositories.task_repo import SqlAlchemyTaskRepository from noteflow.infrastructure.persistence.unit_of_work import SqlAlchemyUnitOfWork from support.db_utils import ( cleanup_test_schema, @@ -111,14 +115,123 @@ async def stopped_meeting_with_segments( meeting.begin_stopping() meeting.stop_recording() await uow.meetings.create(meeting) - segment_0 = Segment(segment_id=0, text="First speaker.", start_time=0.0, end_time=3.0, speaker_id="Alice") - segment_1 = Segment(segment_id=1, text="Second speaker.", start_time=3.0, end_time=6.0, speaker_id="Bob") + segment_0 = Segment( + segment_id=0, text="First speaker.", start_time=0.0, end_time=3.0, speaker_id="Alice" + ) + segment_1 = Segment( + segment_id=1, text="Second speaker.", start_time=3.0, end_time=6.0, speaker_id="Bob" + ) await uow.segments.add(meeting.id, segment_0) await uow.segments.add(meeting.id, segment_1) await uow.commit() return meeting.id +# ============================================================================ +# Task Fixtures (for Task repository tests) +# ============================================================================ + +TASK_WORKSPACE_ID: Final[UUID] = UUID("00000000-0000-0000-0000-000000000099") +"""Fixed workspace ID for task integration tests.""" + + +@pytest.fixture +async def task_workspace(session: AsyncSession) -> UUID: + """Create and persist a workspace for task tests.""" + from noteflow.infrastructure.persistence.models.identity.identity import WorkspaceModel + + workspace = WorkspaceModel() + workspace.id = TASK_WORKSPACE_ID + workspace.name = "Task Test Workspace" + workspace.slug = "task-test-workspace" + workspace.created_at = datetime.now(UTC) + workspace.updated_at = datetime.now(UTC) + session.add(workspace) + await session.flush() + return TASK_WORKSPACE_ID + + +@pytest.fixture +async def task_repo(session: AsyncSession) -> SqlAlchemyTaskRepository: + """Create a task repository instance.""" + return SqlAlchemyTaskRepository(session) + + +@pytest.fixture +def sample_task(task_workspace: UUID) -> Task: + """Create a sample task entity for testing.""" + return Task( + id=uuid4(), + workspace_id=task_workspace, + meeting_id=None, + action_item_id=1, + text="Sample task for testing", + status=TaskStatus.OPEN, + priority=1, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + + +@pytest.fixture +async def persisted_task( + session: AsyncSession, + task_repo: SqlAlchemyTaskRepository, + sample_task: Task, +) -> Task: + """Create and persist a task for tests.""" + await task_repo.create(sample_task) + await session.commit() + return sample_task + + +@pytest.fixture +async def tasks_with_statuses( + session: AsyncSession, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, +) -> tuple[Task, Task, Task]: + """Create tasks with each status type for filtering tests.""" + open_task = Task( + id=uuid4(), + workspace_id=task_workspace, + meeting_id=None, + action_item_id=1, + text="Open task", + status=TaskStatus.OPEN, + priority=1, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + done_task = Task( + id=uuid4(), + workspace_id=task_workspace, + meeting_id=None, + action_item_id=2, + text="Done task", + status=TaskStatus.DONE, + priority=1, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + dismissed_task = Task( + id=uuid4(), + workspace_id=task_workspace, + meeting_id=None, + action_item_id=3, + text="Dismissed task", + status=TaskStatus.DISMISSED, + priority=1, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + await task_repo.create(open_task) + await task_repo.create(done_task) + await task_repo.create(dismissed_task) + await session.commit() + return open_task, done_task, dismissed_task + + # ============================================================================ # Audio Fixtures (for ASR integration tests) # ============================================================================ diff --git a/tests/integration/test_analytics_repository.py b/tests/integration/test_analytics_repository.py new file mode 100644 index 0000000..eed0627 --- /dev/null +++ b/tests/integration/test_analytics_repository.py @@ -0,0 +1,68 @@ +"""Integration tests for SqlAlchemyAnalyticsRepository.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Final +from uuid import uuid4 + +import pytest + +from noteflow.infrastructure.persistence.repositories.analytics_repo import ( + SqlAlchemyAnalyticsRepository, +) + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio import AsyncSession + +EXPECTED_ZERO: Final[int] = 0 + + +@pytest.fixture +def analytics_repo(session: AsyncSession) -> SqlAlchemyAnalyticsRepository: + return SqlAlchemyAnalyticsRepository(session) + + +@pytest.mark.integration +class TestAnalyticsRepositoryOverview: + """Integration tests for analytics overview aggregation.""" + + async def test_get_overview_empty_workspace( + self, + analytics_repo: SqlAlchemyAnalyticsRepository, + ) -> None: + """Test overview returns zeros for workspace with no meetings.""" + workspace_id = uuid4() + + overview = await analytics_repo.get_overview( + workspace_id=workspace_id, + project_ids=None, + start_time=None, + end_time=None, + ) + + assert overview.total_meetings == EXPECTED_ZERO, "expected zero meetings" + assert overview.total_duration == EXPECTED_ZERO, "expected zero duration" + assert overview.total_words == EXPECTED_ZERO, "expected zero words" + assert overview.total_segments == EXPECTED_ZERO, "expected zero segments" + assert overview.speaker_count == EXPECTED_ZERO, "expected zero speakers" + + +@pytest.mark.integration +class TestAnalyticsRepositorySpeakerStats: + """Integration tests for speaker statistics aggregation.""" + + async def test_get_speaker_stats_empty_workspace( + self, + analytics_repo: SqlAlchemyAnalyticsRepository, + ) -> None: + """Test speaker stats returns empty list for workspace with no meetings.""" + workspace_id = uuid4() + + stats = await analytics_repo.get_speaker_stats( + workspace_id=workspace_id, + project_ids=None, + start_time=None, + end_time=None, + ) + + assert len(stats) == EXPECTED_ZERO, "expected no speaker stats" diff --git a/tests/integration/test_task_repository.py b/tests/integration/test_task_repository.py new file mode 100644 index 0000000..5b931ea --- /dev/null +++ b/tests/integration/test_task_repository.py @@ -0,0 +1,245 @@ +"""Integration tests for SqlAlchemyTaskRepository.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import TYPE_CHECKING, Final +from uuid import uuid4 + +import pytest + +from noteflow.domain.entities.task import Task, TaskListFilters, TaskStatus +from noteflow.infrastructure.persistence.repositories.task_repo import SqlAlchemyTaskRepository + +if TYPE_CHECKING: + from uuid import UUID + + from sqlalchemy.ext.asyncio import AsyncSession + +EXPECTED_SINGLE_RESULT: Final[int] = 1 +EXPECTED_ZERO_RESULTS: Final[int] = 0 +EXPECTED_THREE_TASKS: Final[int] = 3 +EXPECTED_TWO_TASKS: Final[int] = 2 +TASK_PRIORITY_LOW: Final[int] = 1 +TASK_PRIORITY_HIGH: Final[int] = 2 +ACTION_ITEM_ID_FIRST: Final[int] = 1 +PAGE_LIMIT_SMALL: Final[int] = 2 + + +@pytest.mark.integration +class TestTaskRepositoryCreate: + """Integration tests for task creation.""" + + async def test_create_and_get_task( + self, + session: AsyncSession, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test creating and retrieving a task.""" + task = Task( + id=uuid4(), + workspace_id=task_workspace, + text="Test task creation", + status=TaskStatus.OPEN, + priority=TASK_PRIORITY_LOW, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + + await task_repo.create(task) + await session.commit() + + retrieved = await task_repo.get(task.id) + + assert retrieved is not None, "task should exist after create" + assert retrieved.id == task.id, "ID should match" + assert retrieved.text == "Test task creation", "text should match" + assert retrieved.status == TaskStatus.OPEN, "expected OPEN status" + assert retrieved.workspace_id == task_workspace, "workspace_id should match" + + +@pytest.mark.integration +class TestTaskRepositoryGet: + """Integration tests for task retrieval.""" + + async def test_get_task_not_found_repository( + self, + task_repo: SqlAlchemyTaskRepository, + ) -> None: + """Test retrieving non-existent task returns None.""" + non_existent_id = uuid4() + + result = await task_repo.get(non_existent_id) + + assert result is None, "expected None for non-existent task" + + async def test_get_persisted_task( + self, + task_repo: SqlAlchemyTaskRepository, + persisted_task: Task, + ) -> None: + """Test retrieving a persisted task.""" + retrieved = await task_repo.get(persisted_task.id) + + assert retrieved is not None, "persisted task should be retrievable" + assert retrieved.text == persisted_task.text, "text should match" + + +@pytest.mark.integration +class TestTaskRepositoryUpdate: + """Integration tests for task updates.""" + + async def test_update_task_status( + self, + session: AsyncSession, + task_repo: SqlAlchemyTaskRepository, + persisted_task: Task, + ) -> None: + """Test updating task status to DONE.""" + updated_task = Task( + id=persisted_task.id, + workspace_id=persisted_task.workspace_id, + text=persisted_task.text, + status=TaskStatus.DONE, + priority=persisted_task.priority, + completed_at=datetime.now(UTC), + created_at=persisted_task.created_at, + updated_at=datetime.now(UTC), + ) + + await task_repo.update(updated_task) + await session.commit() + + retrieved = await task_repo.get(persisted_task.id) + + assert retrieved is not None, "updated task should exist" + assert retrieved.status == TaskStatus.DONE, "expected DONE status" + assert retrieved.completed_at is not None, "completed_at should be set" + + async def test_update_task_not_found_raises( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test updating non-existent task raises ValueError.""" + non_existent_task = Task( + id=uuid4(), + workspace_id=task_workspace, + text="Non-existent", + status=TaskStatus.OPEN, + priority=TASK_PRIORITY_LOW, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + + with pytest.raises(ValueError, match=r"Task .* not found"): + await task_repo.update(non_existent_task) + + +@pytest.mark.integration +class TestTaskRepositoryDelete: + """Integration tests for task deletion.""" + + async def test_delete_task( + self, + session: AsyncSession, + task_repo: SqlAlchemyTaskRepository, + persisted_task: Task, + ) -> None: + """Test deleting an existing task.""" + result = await task_repo.delete(persisted_task.id) + await session.commit() + + assert result is True, "delete should return True for existing task" + + retrieved = await task_repo.get(persisted_task.id) + assert retrieved is None, "task should not exist after deletion" + + async def test_delete_task_not_found_repository( + self, + task_repo: SqlAlchemyTaskRepository, + ) -> None: + """Test deleting non-existent task returns False.""" + non_existent_id = uuid4() + + result = await task_repo.delete(non_existent_id) + + assert result is False, "delete should return False for non-existent task" + + +@pytest.mark.integration +class TestTaskRepositoryList: + """Integration tests for task listing and filtering.""" + + @pytest.mark.usefixtures("tasks_with_statuses") + async def test_list_by_workspace_returns_all_tasks( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test listing all tasks in a workspace.""" + filters = TaskListFilters() + + tasks, total = await task_repo.list_by_workspace(task_workspace, filters) + + assert total == EXPECTED_THREE_TASKS, "expected three total tasks" + assert len(tasks) == EXPECTED_THREE_TASKS, "expected three tasks returned" + + @pytest.mark.usefixtures("tasks_with_statuses") + async def test_list_by_workspace_filter_by_status( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test filtering tasks by status.""" + filters = TaskListFilters(statuses=[TaskStatus.OPEN]) + + tasks, total = await task_repo.list_by_workspace(task_workspace, filters) + + assert total == EXPECTED_SINGLE_RESULT, "expected one OPEN task" + assert len(tasks) == EXPECTED_SINGLE_RESULT, "expected one task returned" + assert tasks[EXPECTED_ZERO_RESULTS].status == TaskStatus.OPEN, ( + "returned task should be OPEN" + ) + + @pytest.mark.usefixtures("tasks_with_statuses") + async def test_list_by_workspace_filter_multiple_statuses( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test filtering tasks by multiple statuses.""" + filters = TaskListFilters(statuses=[TaskStatus.OPEN, TaskStatus.DONE]) + + tasks, total = await task_repo.list_by_workspace(task_workspace, filters) + + assert total == EXPECTED_TWO_TASKS, "expected two tasks (OPEN + DONE)" + assert len(tasks) == EXPECTED_TWO_TASKS, "expected two tasks returned" + + @pytest.mark.usefixtures("tasks_with_statuses") + async def test_list_by_workspace_with_pagination( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test pagination with limit and offset.""" + filters = TaskListFilters(limit=PAGE_LIMIT_SMALL, offset=EXPECTED_ZERO_RESULTS) + + tasks, total = await task_repo.list_by_workspace(task_workspace, filters) + + assert total == EXPECTED_THREE_TASKS, "total should reflect all tasks" + assert len(tasks) == PAGE_LIMIT_SMALL, "expected page size tasks returned" + + async def test_list_by_workspace_empty_result( + self, + task_repo: SqlAlchemyTaskRepository, + task_workspace: UUID, + ) -> None: + """Test listing from empty workspace returns empty list.""" + filters = TaskListFilters() + + tasks, total = await task_repo.list_by_workspace(task_workspace, filters) + + assert total == EXPECTED_ZERO_RESULTS, "expected zero tasks" + assert len(tasks) == EXPECTED_ZERO_RESULTS, "expected empty list"