|
26479
|
1097
|
6
|
2026-05-12T12:31:22.639100+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778589082639_m2.jpg...
|
PhpStorm
|
faVsco.js – console [STAGING]
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.82413566,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"bounds":{"left":0.8394282,"top":0.019952115,"width":0.076130316,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8956692732016058251
|
-8204340751569409078
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
rireroxcalMistorbookmarksProtlles1OOISWindowmelprTavsco.sProjecty© AutomatedReportGenerated.php© PlanhatService.phpM+ README.mdÔ sonar-project.properties= test.py<> Untitled Diagram.xmlI vetur.config.jsM+ WEBHOOK_FILTERING_IMPLEMEI› ib External Librariesv E° Scratches and Consolesv M Database Consoles© AutomatedReportResult.phpclass Irackauсoпaceakeporbeneraredcvent implements shouLauueueV AEUA console [EU]A DEAL RISKS [EUJA DI [EUJAEU (EU]v A jiminny@localhost& console liminny @localhost* Dl liminny@localhostcHs local liminny @localhosA SF [iminny@localhost]< zoho dev liminnvalocalho~ A PRODA console (PROD|IA console PRODIAD PROD› AQAA@AI• APA PRODISTAGING& console [STAGiNGI& console 1 [STAGiNGIA uranus [STAGING]> M SytencionsServices+,o,c~ D DatabaseV dEU&console 3 sv /lminnvalocalhostAHSocal 1s 665 msASFv APROD4 console# STAGING#console 1 s 725 mg…DockerpubLic tunccion nanale Automateakeporcbenerared sevent. volatryforeach ($users as $user) {sthas->userpzlotculent->track suser, Seventame. Soavloadd:•catch CGuzzleExceotion Se) «[Userpilot] Failed to send automated report event', I'report id' => SautomatedRenort->aetido=> $e->getMessage(),$this->release( delay: 3600);* @return array<UserContract>orivare Tunccion resolveusersif ($automatedReport-$Ireturn $creator (== null ? [$creator] : [1;returnsthis-sautonatedrenortsservzce->oetreczozentuserubnectsautomatedrenort^f Outputjiminny_mars.automated_report.resultsO rowsvBid YDuvid Y•0 1Ereport_id Y# Q GEA®InameYImedia_type T@parent_id YIstatus 78101 V1 A V40hl• f Retro - Platform - in 1h 29m100% 58• Tue 12 May 15:31:22E custom.loglaravel.logA SF [jiminny@localhost]PlaygroundSELECT r.* FROM automated_reports rjoin teams t 1..n<->1: on r.team_id = t.idWHERE r.frequency = 'daily'and r.stacus = 1AND t.status = 'active'AND (r.expires_at >= now() OR r.expires_at IS NULL);f ho_local Uiminny@localnost# console [PKol)A console (EU]A console [STAGING] Xdo jiminny_-mars— | 04 A1X13 Aselect * from automated_report results where report 1d IN (18, 3507select * from activity searches where 1d = 109321select * from activity_search_filters where activity_search_id = 10932;select * from automated revorts order by 1d descselect * from automated_report_results order by id desc;select * from automated_reports where id IN (55)select * from automated_report_results where id IN (81);select * from users where id IN 10633.13987. 11985)select * from users where group_id IN (3710);SELECT * FROM automated_reports WHERE uuid_to_bin('18a06a75-afd2-476f-aadc-14d4057bdda2') = uvid;SELECT * FROM automated_report_results WHERE uuid_to_bin('582d4b50-8cd3-42a9-9819-d676ff8f3b43') = uuid;3070a4') = uvid;PSPhpStormcsvv|t→• ộ* D reason 7I payload Tresponse YID requested_at TI generated_at T+ @ sent_at YI created_at Yupdated_at Y...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
26627
|
1102
|
13
|
2026-05-12T12:49:36.285371+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778590176285_m1.jpg...
|
PhpStorm
|
faVsco.js – ReportController.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HandleHubspotRateLimitTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'HandleHubspotRateLimitTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8956692732016058251
|
-8204340751569409078
|
app_switch
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
HandleHubspotRateLimitTest
Run 'HandleHubspotRateLimitTest'
Debug 'HandleHubspotRateLimitTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
SlackFileEditViewGoHistoryWindowHelpRetro - Platform • in 1h 11 mAsshDOCKER• 81DEV (-zsh)О ₴2APP (-zsh)• *[EMAIL]@Lukas-Kovaliks-MacBook-Pro-Jiminny~/.screenpipe/data $ cd ..[EMAIL] $ nasAdm1n@DXP4800PLUS-B5F8: ~$cd/volumel/screenpipe/Adm1n@DXP4800PLUS-B5F8:/volume1/screenpipes 1ltotal26Gdrwxrwxrwx+1rootroot410 May1215:15drwxr-xr-xrootroot450 Apr2519:39drwxrwxrwx+1Adminadmin202 Apr2620:10drwxrwxrwx+1Adminadmin298 May1013:46drwxrwxrwx+Adminadmin144 May09:41drwxrwxrwx+1Admin admin70 May1013:47drwxrwxrwx+1Adminadmin164 Apr1116:51drwxrwxrwx+1rootroot5.1KMay1120:55-rwxrwxrwx+1rootroot31Apr1817:42app_settings.json1Adminadmin13G May1120:55archive.db-rwxrwxrwx+1Adminadmin11G May10-rwxrwxrwx+Admin admin 3.5G May1112:31archive.db-bak20:15db.sqlite-rwxrwxrwx+1Admin admin32K May1205:48db.sqlite-shm-rwxrwxrwx+ 1Admin admin0 Apr 26 17:17db.sqlite-wal1Admin admin11K May 12 09:09.DS_Store-rwxrwxrwx+ 1 Admin admin219 Apr24 19:33•gitignore-rwxrwxrWx+1 Admin admin0 Apr1317:21screenpipe.db-rwxrwxrwx+1 Admin admin 8.4K May12 15:15screenpipe_fts_migrate.sh-rwxrwxrwx+ 1 Admin admin32K May 11 20:48screenpipe_sync.sh-rwxrwxrwx+ 1 Admin admin20K May 10 13:06screenpipe_sync_updated.shAdm1n@DXP4800PLUS-B5F8:/volume1/screenpipe$ cp archive.dbarchive.db.bak-pre-installidAdm1n@DXP4800PLUS-B5F8:/volume1/screenpipes ./screenpipe_fts_migrate.sh archive.dbScreenpipe FTS migrationDB:archive.dbSize: 13G• ×4-zsh• 285screenpipe"O 886ssh100% <78•Tue 12 May 15:49:36T8187-zsh• *8+• Creating install registry_installs table• Om01s• Adding install_id to base tablesvideo_chunksalready presentError: stepping, UNIQUEconstraintfailed: video_chunks.install_id, video_chunks.id (19)Adm1n@DXP4800PLUS-B5F8:/volume1/screenpipes sqlite3 archive.db "SELECT * FROM_installs;"Adm1neDXP4800PLUS-B5F8:/volume1/screenpipes sqlite3 archive.db "SELECT install_id, COUNT(*) FROM frames GROUP BY install_id;"Error: in prepare, no such column: install_idSELECT install_id,COUNT(*) FROM frames GROUP BY install_id;^_-- error hereAdm1n@DXP4800PLUS-B5F8:/volume1/screenpipes |...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
17648
|
776
|
0
|
2026-05-11T10:27:10.085397+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778495230085_m1.jpg...
|
Firefox
|
Screenpipe — Archive — Personal
|
True
|
app.screenpipe.lakylak.xyz
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Screenpipe — Archive
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"on_screen":true,"help_text":"","role_description":"tab","is_enabled":true,"is_focused":false,"is_selected":true}]...
|
8955950182488785941
|
-7763056176263877743
|
click
|
hybrid
|
NULL
|
New Tab
New Tab
Screenpipe — Archive
iTerm2ShellEd New Tab
New Tab
Screenpipe — Archive
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpDOCKERS'381DEV (docker)Last login: Sun May 10 21:10:26 on ttys013₴2APP (-zsh)883Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ 0-zsh-zshlabol# Support Daily - in 1h 33 m100% <7*4-zsh*5screenpipe"8• Mon 11 May 13:27:09181-zsh*7...
|
17642
|
NULL
|
NULL
|
NULL
|
|
14470
|
643
|
30
|
2026-05-09T17:59:45.031909+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-09/1778 /Users/lukas/.screenpipe/data/data/2026-05-09/1778349585031_m1.jpg...
|
iTerm2
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
CodeFileEditSelectionView→Nginx Proxy Manager@Scre CodeFileEditSelectionView→Nginx Proxy Manager@Screenpipe - ArchiveSQLite Web: archive.dbSQLite Web: db.sqlitescreenpipe/.claude/skills at main - screenpipe/screenpDXP4800PLUS-B5F8AFFiNE - All In One KnowledgeOSAll docs • AFFiNEPayments LoggerM Your old PC can run Windows 11 in a VM, but not on baLocation LoggerFinance HubFinance HubSelect: transactions - db - AdminerClaude Code | Claude Platform* April 2026 spending by category - Claudelakylak/finance-hub - finance-hub - Gitea: Git with a c• Applications - Admin - authentik+ New TabGoRunTerminalWindow Helpnas.lakylak.xyz/desktop/?os=ugospro#/FilesControl PanelPRI8StorageApp Center/logsLogsSupportQ Search|Connection & AccessUser ManagementFile ServiceW DeviceConnectionDomain/LDAP• TerminalGeneral|Hardware & PowerTime & Language- NetworkSecurityE Indexing ServiceService• About‹ $0lallE: Control Panel100% C8Sat 9 May 20:59:45нАм Ід.ТквмQ?=TelnetSSHEnablePort 23|Advanced settings• EnablePort22Shut down automatically3h later2026-05-09 21:18 will automatically shut downAdvanced settingsFunction descriptionyaswvord fir tie logi accoum ange nabie aute ok eo enhanice sysfeum secuitis recomended to sel a strongApply...
|
NULL
|
8955534055665012397
|
NULL
|
visual_change
|
ocr
|
NULL
|
CodeFileEditSelectionView→Nginx Proxy Manager@Scre CodeFileEditSelectionView→Nginx Proxy Manager@Screenpipe - ArchiveSQLite Web: archive.dbSQLite Web: db.sqlitescreenpipe/.claude/skills at main - screenpipe/screenpDXP4800PLUS-B5F8AFFiNE - All In One KnowledgeOSAll docs • AFFiNEPayments LoggerM Your old PC can run Windows 11 in a VM, but not on baLocation LoggerFinance HubFinance HubSelect: transactions - db - AdminerClaude Code | Claude Platform* April 2026 spending by category - Claudelakylak/finance-hub - finance-hub - Gitea: Git with a c• Applications - Admin - authentik+ New TabGoRunTerminalWindow Helpnas.lakylak.xyz/desktop/?os=ugospro#/FilesControl PanelPRI8StorageApp Center/logsLogsSupportQ Search|Connection & AccessUser ManagementFile ServiceW DeviceConnectionDomain/LDAP• TerminalGeneral|Hardware & PowerTime & Language- NetworkSecurityE Indexing ServiceService• About‹ $0lallE: Control Panel100% C8Sat 9 May 20:59:45нАм Ід.ТквмQ?=TelnetSSHEnablePort 23|Advanced settings• EnablePort22Shut down automatically3h later2026-05-09 21:18 will automatically shut downAdvanced settingsFunction descriptionyaswvord fir tie logi accoum ange nabie aute ok eo enhanice sysfeum secuitis recomended to sel a strongApply...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
25732
|
1079
|
30
|
2026-05-12T11:47:45.956358+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778586465956_m2.jpg...
|
Firefox
|
TypeError: League\Flysystem\Filesystem::has(): Arg TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app — Work...
|
True
|
jiminny.sentry.io/issues/6873095751/events/?projec jiminny.sentry.io/issues/6873095751/events/?project=82419&statsPeriod=24h...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Close tab
CloudWatch | us-east-2
CloudWatch | us-east-2
Pipelines - jiminny/app
Pipelines - jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
Userpilot | Events
Userpilot | Events
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to main content
Skip to main content
Toggle organization menu
Issues
Issues
Explore
Explore
Dashboards
Dashboards
Monitors
Monitors
Settings
Settings
Try Business
What's New
Help
[EMAIL]
Issues
Expand
Feed
Feed
Errors & Outages
Errors & Outages
Breached Metrics
Breached Metrics
Warnings
Warnings
User Feedback
User Feedback
Autofix
Autofix
Recently Run
Recently Run
All Views
All Views
Configure
Alerts Moved
Alerts
Moved
Issues
Issues
View Project Details
APP-1DTF
Ask Seer
Ask Seer
/
Give Feedback
TypeError
View events
Events (total)
Users (90d)
Level: Error
League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218
2.4K
0
Ongoing
/app/Jobs/AutomatedReports/SendReportJob.php in Jiminny\Jobs\AutomatedReports\SendReportJob::handle
Quick Fix
Resolve
Resolve
More resolve options
Archive
Archive
Archive options
Subscribe
Share
More Actions
Priority
Modify issue priority
High
Assignee
Modify issue assignee
Nikolay Nikolov
All Envs
All Envs
24H
24H
Add a search term
Add a search term
Close sidebar
Toggle graph series - Events
Events
66
Toggle graph series - Users
Users
0
release 9% 881233
release
9%
881233
environment 73% production
environment
73%
production
region 100% us-east-2
region
100%
us-east-2
os.name 100% Linux
os.name
100%
Linux
View all tags
View all tags
Select issue content
Events
Open in Discover
Open in Discover
Return to event details
Close
All Events
Showing
1
-
50
of
66
matching
events
Previous Page
Next Page
Event ID
Timestamp
Timestamp
Title
Title
Transaction
Transaction
Release
Release
Environment
Environment
User
User
Device
Device
OS
OS
URL
URL
Runtime
Runtime
Replay
Trace
Actions
90670ec9
90670ec9
Actions
May 12, 2026 11:00:12 AM UTC
Actions
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218
Actions
(empty string)
Actions
884736
884736
Actions
staging-mars
Actions
(no value)
Actions
Actions
Linux 6.1.164-196.303.amzn2023.aarch64
Actions
(empty string)
Actions
php 8.5.5
Actions...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Unnamed Group","depth":4,"bounds":{"left":0.0028257978,"top":0.057063047,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"bounds":{"left":0.0028257978,"top":0.08060654,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"bounds":{"left":0.015957447,"top":0.09217877,"width":0.40492022,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.087789305,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.11332801,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.12490024,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.14604948,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.15762171,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"bounds":{"left":0.0,"top":0.17877094,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.19034317,"width":0.16140293,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21149242,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22306465,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"bounds":{"left":0.0,"top":0.2442139,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.25578612,"width":0.1200133,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.27693537,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.28850758,"width":0.1931516,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"bounds":{"left":0.0,"top":0.30965683,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32122904,"width":0.07646277,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.3423783,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.35395053,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":4,"bounds":{"left":0.0,"top":0.37509975,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6793] Les Mills activity types not pulling in - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.386672,"width":0.09524601,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Team - Backlog - Jira","depth":4,"bounds":{"left":0.0,"top":0.40782124,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team - Backlog - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41939345,"width":0.053025264,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Events","depth":4,"bounds":{"left":0.0,"top":0.4405427,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Events","depth":5,"bounds":{"left":0.013297873,"top":0.4521149,"width":0.030418882,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.47486034,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to main content","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to main content","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Toggle organization menu","depth":11,"bounds":{"left":0.08643617,"top":0.059856344,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues","depth":12,"bounds":{"left":0.0809508,"top":0.09736632,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"bounds":{"left":0.0866024,"top":0.13048683,"width":0.010305851,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Explore","depth":12,"bounds":{"left":0.0809508,"top":0.14804469,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Explore","depth":14,"bounds":{"left":0.08577128,"top":0.1811652,"width":0.011968086,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Dashboards","depth":12,"bounds":{"left":0.0809508,"top":0.19872306,"width":0.021609042,"height":0.05027933},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboards","depth":14,"bounds":{"left":0.08211436,"top":0.23184358,"width":0.019281914,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Monitors","depth":12,"bounds":{"left":0.0809508,"top":0.2490024,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Monitors","depth":14,"bounds":{"left":0.084773935,"top":0.2821229,"width":0.013962766,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.0809508,"top":0.29968077,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.08494016,"top":0.33280128,"width":0.013630319,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Try Business","depth":10,"bounds":{"left":0.08643617,"top":0.88667196,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"What's New","depth":10,"bounds":{"left":0.08643617,"top":0.9114126,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":10,"bounds":{"left":0.08643617,"top":0.93615323,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"lukas.kovalik@jiminny.com","depth":10,"bounds":{"left":0.08643617,"top":0.9680766,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Issues","depth":12,"bounds":{"left":0.04305186,"top":0.066640064,"width":0.014461436,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":12,"bounds":{"left":0.088597074,"top":0.061452515,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Feed","depth":14,"bounds":{"left":0.039727394,"top":0.10055866,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed","depth":16,"bounds":{"left":0.044049203,"top":0.10734238,"width":0.010638298,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Errors & Outages","depth":14,"bounds":{"left":0.039727394,"top":0.14046289,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Errors & Outages","depth":16,"bounds":{"left":0.044049203,"top":0.14724661,"width":0.03673537,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Breached Metrics","depth":14,"bounds":{"left":0.039727394,"top":0.16759777,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Breached Metrics","depth":16,"bounds":{"left":0.044049203,"top":0.17438148,"width":0.037898935,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Warnings","depth":14,"bounds":{"left":0.039727394,"top":0.19473264,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Warnings","depth":16,"bounds":{"left":0.044049203,"top":0.20151636,"width":0.019946808,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"User Feedback","depth":14,"bounds":{"left":0.039727394,"top":0.22186752,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"User Feedback","depth":16,"bounds":{"left":0.044049203,"top":0.22865124,"width":0.032081116,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Autofix","depth":12,"bounds":{"left":0.039727394,"top":0.26177174,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Autofix","depth":15,"bounds":{"left":0.043716755,"top":0.26855546,"width":0.016289894,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Recently Run","depth":14,"bounds":{"left":0.039727394,"top":0.28731045,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Recently Run","depth":16,"bounds":{"left":0.044049203,"top":0.29409418,"width":0.028922873,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"All Views","depth":14,"bounds":{"left":0.039727394,"top":0.3272147,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All Views","depth":16,"bounds":{"left":0.044049203,"top":0.3339984,"width":0.019281914,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Configure","depth":13,"bounds":{"left":0.043716755,"top":0.3735036,"width":0.021941489,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Alerts Moved","depth":14,"bounds":{"left":0.039727394,"top":0.39225858,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Alerts","depth":16,"bounds":{"left":0.044049203,"top":0.3990423,"width":0.012799202,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Moved","depth":16,"bounds":{"left":0.08045213,"top":0.39984038,"width":0.012466756,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Issues","depth":12,"bounds":{"left":0.10954122,"top":0.06464485,"width":0.013796543,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"bounds":{"left":0.10954122,"top":0.066640064,"width":0.013796543,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View Project Details","depth":13,"bounds":{"left":0.1299867,"top":0.06624102,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"APP-1DTF","depth":16,"bounds":{"left":0.13796543,"top":0.066640064,"width":0.021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Ask Seer","depth":10,"bounds":{"left":0.93484044,"top":0.059856344,"width":0.04720745,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Seer","depth":13,"bounds":{"left":0.9461436,"top":0.0650439,"width":0.019614361,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":14,"bounds":{"left":0.9740692,"top":0.065442935,"width":0.0021609042,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Give Feedback","depth":11,"bounds":{"left":0.9840425,"top":0.059856344,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError","depth":13,"bounds":{"left":0.10954122,"top":0.10295291,"width":0.03174867,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View events","depth":13,"bounds":{"left":0.9409907,"top":0.10654429,"width":0.026097074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":true,"is_selected":false},{"role":"AXStaticText","text":"Events (total)","depth":14,"bounds":{"left":0.9409907,"top":0.10654429,"width":0.026097074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Users (90d)","depth":13,"bounds":{"left":0.9724069,"top":0.10654429,"width":0.022273935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Level: Error","depth":15,"bounds":{"left":0.10920878,"top":0.12490024,"width":0.02443484,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218","depth":14,"bounds":{"left":0.11253324,"top":0.12490024,"width":0.453125,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2.4K","depth":13,"bounds":{"left":0.9534575,"top":0.12210695,"width":0.013630319,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":13,"bounds":{"left":0.99052525,"top":0.12210695,"width":0.004155585,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ongoing","depth":14,"bounds":{"left":0.10954122,"top":0.14046289,"width":0.018118352,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Jobs/AutomatedReports/SendReportJob.php in Jiminny\\Jobs\\AutomatedReports\\SendReportJob::handle","depth":13,"bounds":{"left":0.13447474,"top":0.14046289,"width":0.24534574,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Quick Fix","depth":14,"bounds":{"left":0.3912899,"top":0.14046289,"width":0.019614361,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Resolve","depth":12,"bounds":{"left":0.10954122,"top":0.16719872,"width":0.02543218,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Resolve","depth":14,"bounds":{"left":0.11353058,"top":0.17238627,"width":0.017453458,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More resolve options","depth":12,"bounds":{"left":0.13464096,"top":0.16719872,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Archive","depth":12,"bounds":{"left":0.14660904,"top":0.16719872,"width":0.025265958,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archive","depth":14,"bounds":{"left":0.1505984,"top":0.17238627,"width":0.017287234,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Archive options","depth":12,"bounds":{"left":0.17154256,"top":0.16719872,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Subscribe","depth":12,"bounds":{"left":0.18351063,"top":0.16719872,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share","depth":12,"bounds":{"left":0.19547872,"top":0.16719872,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More Actions","depth":12,"bounds":{"left":0.20744681,"top":0.16719872,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Priority","depth":12,"bounds":{"left":0.89544547,"top":0.17398244,"width":0.015791224,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Modify issue priority","depth":12,"bounds":{"left":0.9125665,"top":0.17039107,"width":0.013962766,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"High","depth":17,"bounds":{"left":0.91954786,"top":0.18076617,"width":0.008976064,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Assignee","depth":12,"bounds":{"left":0.9318484,"top":0.17398244,"width":0.019946808,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Modify issue assignee","depth":13,"bounds":{"left":0.953125,"top":0.16999201,"width":0.04155585,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Nikolay Nikolov","depth":17,"bounds":{"left":0.9616024,"top":0.17398244,"width":0.027593086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"All Envs","depth":13,"bounds":{"left":0.10954122,"top":0.20949721,"width":0.03158245,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"All Envs","depth":17,"bounds":{"left":0.11353058,"top":0.21628092,"width":0.01761968,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"24H","depth":13,"bounds":{"left":0.14079122,"top":0.20949721,"width":0.023105053,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"24H","depth":17,"bounds":{"left":0.14478059,"top":0.21628092,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Add a search term","depth":16,"bounds":{"left":0.17719415,"top":0.21428572,"width":0.69714093,"height":0.01915403},"on_screen":true,"help_text":"","placeholder":"Filter events…","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Add a search term","depth":16,"bounds":{"left":0.17719415,"top":0.21428572,"width":0.69714093,"height":0.01915403},"on_screen":true,"help_text":"","placeholder":"Filter events…","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close sidebar","depth":13,"bounds":{"left":0.88264626,"top":0.20949721,"width":0.011968086,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Toggle graph series - Events","depth":12,"bounds":{"left":0.11386303,"top":0.25818038,"width":0.021276595,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Events","depth":15,"bounds":{"left":0.1178524,"top":0.26256984,"width":0.013297873,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"66","depth":15,"bounds":{"left":0.12117686,"top":0.27693537,"width":0.0066489363,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Toggle graph series - Users","depth":12,"bounds":{"left":0.11386303,"top":0.2980846,"width":0.021276595,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Users","depth":15,"bounds":{"left":0.119015954,"top":0.3008779,"width":0.010970744,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":15,"bounds":{"left":0.12267287,"top":0.31524342,"width":0.0034906915,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"release 9% 881233","depth":12,"bounds":{"left":0.7669548,"top":0.25818038,"width":0.11702128,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"release","depth":14,"bounds":{"left":0.76894945,"top":0.25977653,"width":0.013962766,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9%","depth":14,"bounds":{"left":0.845246,"top":0.25977653,"width":0.0056515955,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"881233","depth":14,"bounds":{"left":0.8522274,"top":0.25977653,"width":0.013796543,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"environment 73% production","depth":12,"bounds":{"left":0.7669548,"top":0.2725459,"width":0.11702128,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"environment","depth":14,"bounds":{"left":0.76894945,"top":0.273743,"width":0.024767287,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"73%","depth":14,"bounds":{"left":0.8429189,"top":0.273743,"width":0.007978723,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"production","depth":14,"bounds":{"left":0.8522274,"top":0.273743,"width":0.020279255,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"region 100% us-east-2","depth":12,"bounds":{"left":0.7669548,"top":0.2869114,"width":0.11702128,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"region","depth":14,"bounds":{"left":0.76894945,"top":0.28810853,"width":0.012300532,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":14,"bounds":{"left":0.84042555,"top":0.28810853,"width":0.010472074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"us-east-2","depth":14,"bounds":{"left":0.8522274,"top":0.28810853,"width":0.018284574,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"os.name 100% Linux","depth":12,"bounds":{"left":0.7669548,"top":0.3008779,"width":0.11702128,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"os.name","depth":14,"bounds":{"left":0.76894945,"top":0.30247405,"width":0.016289894,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"100%","depth":14,"bounds":{"left":0.84042555,"top":0.30247405,"width":0.010472074,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Linux","depth":14,"bounds":{"left":0.8522274,"top":0.30247405,"width":0.010139627,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View all tags","depth":12,"bounds":{"left":0.76894945,"top":0.31763768,"width":0.027094414,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View all tags","depth":13,"bounds":{"left":0.76894945,"top":0.31923383,"width":0.027094414,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Select issue content","depth":13,"bounds":{"left":0.10954122,"top":0.34836394,"width":0.028922873,"height":0.025538707},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Events","depth":15,"bounds":{"left":0.11353058,"top":0.35434955,"width":0.01761968,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in Discover","depth":13,"bounds":{"left":0.82430184,"top":0.3499601,"width":0.04338431,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Open in Discover","depth":15,"bounds":{"left":0.83294547,"top":0.35514766,"width":0.032081116,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Return to event details","depth":13,"bounds":{"left":0.8703458,"top":0.3499601,"width":0.015957447,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Close","depth":15,"bounds":{"left":0.87300533,"top":0.35514766,"width":0.010638298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All Events","depth":14,"bounds":{"left":0.11386303,"top":0.38946527,"width":0.022273935,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Showing","depth":13,"bounds":{"left":0.79172206,"top":0.39026338,"width":0.017121011,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":13,"bounds":{"left":0.8088431,"top":0.39026338,"width":0.0016622341,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"-","depth":13,"bounds":{"left":0.81050533,"top":0.39026338,"width":0.0018284575,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50","depth":13,"bounds":{"left":0.81233376,"top":0.39026338,"width":0.004986702,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"of","depth":13,"bounds":{"left":0.81732047,"top":0.39026338,"width":0.0056515955,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"66","depth":13,"bounds":{"left":0.82297206,"top":0.39026338,"width":0.0048204786,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"matching","depth":13,"bounds":{"left":0.8277925,"top":0.39026338,"width":0.019780586,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"events","depth":13,"bounds":{"left":0.84757316,"top":0.39026338,"width":0.012466756,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Previous Page","depth":13,"bounds":{"left":0.8640292,"top":0.38427773,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Next Page","depth":13,"bounds":{"left":0.87400264,"top":0.38427773,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Event ID","depth":16,"bounds":{"left":0.11386303,"top":0.42218676,"width":0.018783245,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Timestamp","depth":15,"bounds":{"left":0.1471077,"top":0.42218676,"width":0.06482713,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Timestamp","depth":16,"bounds":{"left":0.1471077,"top":0.42218676,"width":0.025099734,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Title","depth":15,"bounds":{"left":0.22024602,"top":0.42218676,"width":0.05817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Title","depth":16,"bounds":{"left":0.22024602,"top":0.42218676,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Transaction","depth":15,"bounds":{"left":0.2867354,"top":0.42218676,"width":0.05817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Transaction","depth":16,"bounds":{"left":0.2867354,"top":0.42218676,"width":0.026595745,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Release","depth":15,"bounds":{"left":0.35322472,"top":0.42218676,"width":0.04155585,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Release","depth":16,"bounds":{"left":0.35322472,"top":0.42218676,"width":0.01761968,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Environment","depth":15,"bounds":{"left":0.40309176,"top":0.42218676,"width":0.029920213,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Environment","depth":16,"bounds":{"left":0.40309176,"top":0.42218676,"width":0.029089095,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"User","depth":15,"bounds":{"left":0.44132313,"top":0.42218676,"width":0.04155585,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"User","depth":16,"bounds":{"left":0.44132313,"top":0.42218676,"width":0.010472074,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Device","depth":15,"bounds":{"left":0.49119017,"top":0.42218676,"width":0.04155585,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Device","depth":16,"bounds":{"left":0.49119017,"top":0.42218676,"width":0.01512633,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"OS","depth":15,"bounds":{"left":0.54105717,"top":0.42218676,"width":0.04155585,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"OS","depth":16,"bounds":{"left":0.54105717,"top":0.42218676,"width":0.006150266,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"URL","depth":15,"bounds":{"left":0.5909242,"top":0.42218676,"width":0.09142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"URL","depth":16,"bounds":{"left":0.5909242,"top":0.42218676,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Runtime","depth":15,"bounds":{"left":0.6906583,"top":0.42218676,"width":0.04155585,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Runtime","depth":16,"bounds":{"left":0.6906583,"top":0.42218676,"width":0.019115692,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replay","depth":16,"bounds":{"left":0.74052525,"top":0.42218676,"width":0.01512633,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trace","depth":16,"bounds":{"left":0.77377,"top":0.42218676,"width":0.012466756,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.11386303,"top":0.4501197,"width":0.025265958,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"90670ec9","depth":16,"bounds":{"left":0.11386303,"top":0.4501197,"width":0.025265958,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"90670ec9","depth":18,"bounds":{"left":0.11386303,"top":0.4517159,"width":0.021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.1471077,"top":0.44972068,"width":0.065159574,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"May 12, 2026 11:00:12 AM UTC","depth":19,"bounds":{"left":0.1471077,"top":0.4521149,"width":0.068484046,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.22024602,"top":0.4501197,"width":0.05851064,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218","depth":18,"bounds":{"left":0.22024602,"top":0.4517159,"width":0.47639626,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.2867354,"top":0.44972068,"width":0.05851064,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"(empty string)","depth":17,"bounds":{"left":0.2867354,"top":0.4521149,"width":0.030585106,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.35322472,"top":0.4501197,"width":0.041888297,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"884736","depth":18,"bounds":{"left":0.35322472,"top":0.4501197,"width":0.017287234,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"884736","depth":20,"bounds":{"left":0.35322472,"top":0.4517159,"width":0.017287234,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.40309176,"top":0.4501197,"width":0.03025266,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"staging-mars","depth":18,"bounds":{"left":0.40309176,"top":0.4517159,"width":0.029421542,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.44132313,"top":0.44972068,"width":0.041888297,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"(no value)","depth":17,"bounds":{"left":0.44132313,"top":0.4521149,"width":0.020944148,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.54105717,"top":0.4501197,"width":0.041888297,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Linux 6.1.164-196.303.amzn2023.aarch64","depth":17,"bounds":{"left":0.5490359,"top":0.4517159,"width":0.09009308,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.5909242,"top":0.44972068,"width":0.091755316,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"(empty string)","depth":17,"bounds":{"left":0.5909242,"top":0.4521149,"width":0.030585106,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.6906583,"top":0.4501197,"width":0.041888297,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"php 8.5.5","depth":18,"bounds":{"left":0.6906583,"top":0.4517159,"width":0.020279255,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Actions","depth":15,"bounds":{"left":0.74052525,"top":0.4501197,"width":0.025265958,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8953705158543464639
|
6501318400373258468
|
visual_change
|
accessibility
|
NULL
|
Unnamed Group
TypeError: League\Flysystem\Filesyst Unnamed Group
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Close tab
CloudWatch | us-east-2
CloudWatch | us-east-2
Pipelines - jiminny/app
Pipelines - jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking for automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
[SRD-6793] Les Mills activity types not pulling in - Jira
[SRD-6793] Les Mills activity types not pulling in - Jira
Platform Team - Backlog - Jira
Platform Team - Backlog - Jira
Userpilot | Events
Userpilot | Events
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to main content
Skip to main content
Toggle organization menu
Issues
Issues
Explore
Explore
Dashboards
Dashboards
Monitors
Monitors
Settings
Settings
Try Business
What's New
Help
[EMAIL]
Issues
Expand
Feed
Feed
Errors & Outages
Errors & Outages
Breached Metrics
Breached Metrics
Warnings
Warnings
User Feedback
User Feedback
Autofix
Autofix
Recently Run
Recently Run
All Views
All Views
Configure
Alerts Moved
Alerts
Moved
Issues
Issues
View Project Details
APP-1DTF
Ask Seer
Ask Seer
/
Give Feedback
TypeError
View events
Events (total)
Users (90d)
Level: Error
League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218
2.4K
0
Ongoing
/app/Jobs/AutomatedReports/SendReportJob.php in Jiminny\Jobs\AutomatedReports\SendReportJob::handle
Quick Fix
Resolve
Resolve
More resolve options
Archive
Archive
Archive options
Subscribe
Share
More Actions
Priority
Modify issue priority
High
Assignee
Modify issue assignee
Nikolay Nikolov
All Envs
All Envs
24H
24H
Add a search term
Add a search term
Close sidebar
Toggle graph series - Events
Events
66
Toggle graph series - Users
Users
0
release 9% 881233
release
9%
881233
environment 73% production
environment
73%
production
region 100% us-east-2
region
100%
us-east-2
os.name 100% Linux
os.name
100%
Linux
View all tags
View all tags
Select issue content
Events
Open in Discover
Open in Discover
Return to event details
Close
All Events
Showing
1
-
50
of
66
matching
events
Previous Page
Next Page
Event ID
Timestamp
Timestamp
Title
Title
Transaction
Transaction
Release
Release
Environment
Environment
User
User
Device
Device
OS
OS
URL
URL
Runtime
Runtime
Replay
Trace
Actions
90670ec9
90670ec9
Actions
May 12, 2026 11:00:12 AM UTC
Actions
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218
Actions
(empty string)
Actions
884736
884736
Actions
staging-mars
Actions
(no value)
Actions
Actions
Linux 6.1.164-196.303.amzn2023.aarch64
Actions
(empty string)
Actions
php 8.5.5
Actions...
|
25731
|
NULL
|
NULL
|
NULL
|
|
13386
|
592
|
0
|
2026-05-09T15:23:53.860325+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-09/1778 /Users/lukas/.screenpipe/data/data/2026-05-09/1778340233860_m2.jpg...
|
Firefox
|
Finance Hub — Personal
|
True
|
finance-hub.lakylak.xyz/outpost.goauthentik.io/sig finance-hub.lakylak.xyz/outpost.goauthentik.io/sign_out...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
(25) Quora
(25) Quora
Location Logger
Location Logger
Finance Hub
Finance Hub
Finance Hub
Finance Hub
Close tab
Select: transactions - db - Adminer
Select: transactions - db - Adminer
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE
Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE
VIVACOM
VIVACOM
Смартфони с Unlimited план до 120 € отстъпка | Vivacom
Смартфони с Unlimited план до 120 € отстъпка | Vivacom
VIVACOM
VIVACOM
Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom
Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom
Claude Code | Claude Platform
Claude Code | Claude Platform
Claude
Claude
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Finance Hub
Finance Hub
187
transaction
s
Payments
Payments
Upload CSV
Upload CSV
Refresh
Display settings
Sign out
Filters
Filters
Search
Recipient, description…
Source
Status
Type
Tag
From date
dd
/
mm
/
yyyy
Calendar
To date
dd
/
mm
/
yyyy
Calendar
187
transaction
s
(balance alerts hidden)
·
Total:
10,250.48
EUR
DATE
RECIPIENT
AMOUNT
TAGS
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
RAW DATA
Zdraveyte, ot 01/07/2026 vlizat v sila izmeneni Obshti uslovia, a ot 01/09/2026 - izmenena Tarifa na Banka DSK. Poveche na sayta na bankata, sektsia Izvestia.
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
09 May 2026, 15:02
VIADENTAL, SOFIA, BG
Raw data
124.00 EUR
Send
Send
Skip
Skip
Delete
09 May 2026, 14:54
VIADENTAL, SOFIA, BG
Raw data
117.00 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 19:32
LIDL BALGARIYA EOOD, SOFIYA, BGR
Raw data
67.81 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:45
Sinsay, Sofia, BG
Raw data
5.02 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:35
TEMA RETAIL BG EOOD, SOFIA, BG
Raw data
15.46 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:07
DM BULGARIA EOOD, SOFIYA, BG
Raw data
9.04 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 10:00
DSK ATM, SOFIA, BG
Raw data
200.00 EUR
Send
Send
Skip
Skip
Delete
07 May 2026, 09:02
CBA EKO MARKET, SOFIA, BG
Raw data
5.51 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 19:02
CBA ECO MARKET, SOFIA, BG
Raw data
5.93 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 18:40
FANTASTICO GROUP LTD, SOFIA, BG
Raw data
13.02 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 17:19
TAXI 065, SOFIA, BG
Raw data
17.00 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 13:16
Lagardere Travel R
Raw data
5.49 EUR
Send
Send
Skip
Skip
Delete
05 May 2026, 18:55
dennikn
Raw data
7.99 EUR
Send
Send
Skip
Skip
Delete
05 May 2026, 18:05
Dr
Raw data
1.28 EUR...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.080784574,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DNS / Nameservers | Hostinger","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DNS / Nameservers | Hostinger","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.053856384,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Nginx Proxy Manager","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Nginx Proxy Manager","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: archive.db","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.040724736,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.03756649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.11469415,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"AFFiNE - All In One KnowledgeOS","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"AFFiNE - All In One KnowledgeOS","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.05851064,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"All docs · AFFiNE","depth":4,"bounds":{"left":0.0,"top":0.3463687,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All docs · AFFiNE","depth":5,"bounds":{"left":0.013297873,"top":0.3575419,"width":0.029587766,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Payments Logger","depth":4,"bounds":{"left":0.0,"top":0.3790902,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Payments Logger","depth":5,"bounds":{"left":0.013297873,"top":0.39026338,"width":0.030086435,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":4,"bounds":{"left":0.0,"top":0.41181165,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox (2) - kovaliklukas@gmail.com - Gmail","depth":5,"bounds":{"left":0.013297873,"top":0.42298484,"width":0.07513298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"(25) Quora","depth":4,"bounds":{"left":0.0,"top":0.4445331,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"(25) Quora","depth":5,"bounds":{"left":0.013297873,"top":0.4557063,"width":0.018949468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Location Logger","depth":4,"bounds":{"left":0.0,"top":0.4772546,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Location Logger","depth":5,"bounds":{"left":0.013297873,"top":0.4884278,"width":0.028091755,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"bounds":{"left":0.0,"top":0.509976,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Finance Hub","depth":5,"bounds":{"left":0.013297873,"top":0.5211492,"width":0.021609042,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"bounds":{"left":0.0,"top":0.54269755,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Finance Hub","depth":5,"bounds":{"left":0.013297873,"top":0.55387074,"width":0.021609042,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.10139628,"top":0.54988027,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Select: transactions - db - Adminer","depth":4,"bounds":{"left":0.0,"top":0.575419,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Select: transactions - db - Adminer","depth":5,"bounds":{"left":0.013297873,"top":0.5865922,"width":0.061170213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Електронно банкиране ДСК Директ от Банка ДСК","depth":4,"bounds":{"left":0.0,"top":0.60814047,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Електронно банкиране ДСК Директ от Банка ДСК","depth":5,"bounds":{"left":0.013297873,"top":0.61931366,"width":0.09059176,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE","depth":4,"bounds":{"left":0.0,"top":0.6408619,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE","depth":5,"bounds":{"left":0.013297873,"top":0.6520351,"width":0.113696806,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"VIVACOM","depth":4,"bounds":{"left":0.0,"top":0.6735834,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"VIVACOM","depth":5,"bounds":{"left":0.013297873,"top":0.6847566,"width":0.016788565,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Смартфони с Unlimited план до 120 € отстъпка | Vivacom","depth":4,"bounds":{"left":0.0,"top":0.70630485,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Смартфони с Unlimited план до 120 € отстъпка | Vivacom","depth":5,"bounds":{"left":0.013297873,"top":0.71747804,"width":0.10239362,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"VIVACOM","depth":4,"bounds":{"left":0.0,"top":0.7390263,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"VIVACOM","depth":5,"bounds":{"left":0.013297873,"top":0.7501995,"width":0.016788565,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom","depth":4,"bounds":{"left":0.0,"top":0.7717478,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom","depth":5,"bounds":{"left":0.013297873,"top":0.782921,"width":0.098902926,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Claude Code | Claude Platform","depth":4,"bounds":{"left":0.0,"top":0.8044693,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Claude Code | Claude Platform","depth":5,"bounds":{"left":0.013297873,"top":0.8156425,"width":0.053357713,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Claude","depth":4,"bounds":{"left":0.0,"top":0.83719075,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Claude","depth":5,"bounds":{"left":0.013297873,"top":0.84836394,"width":0.012134309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.87150836,"width":0.108211435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Finance Hub","depth":8,"bounds":{"left":0.13829787,"top":0.0622506,"width":0.032081116,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Finance Hub","depth":9,"bounds":{"left":0.13829787,"top":0.06264964,"width":0.032081116,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"187","depth":9,"bounds":{"left":0.13829787,"top":0.07861133,"width":0.0066489363,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"transaction","depth":9,"bounds":{"left":0.14494681,"top":0.07861133,"width":0.022273935,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"s","depth":9,"bounds":{"left":0.16722074,"top":0.07861133,"width":0.0021609042,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Payments","depth":8,"bounds":{"left":0.27310506,"top":0.06384677,"width":0.036070477,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Payments","depth":10,"bounds":{"left":0.28374335,"top":0.0698324,"width":0.02144282,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Upload CSV","depth":8,"bounds":{"left":0.3098404,"top":0.06384677,"width":0.04105718,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Upload CSV","depth":10,"bounds":{"left":0.32047874,"top":0.0698324,"width":0.02642952,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Refresh","depth":8,"bounds":{"left":0.45345744,"top":0.06384677,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Display settings","depth":8,"bounds":{"left":0.4660904,"top":0.06384677,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Sign out","depth":8,"bounds":{"left":0.4787234,"top":0.06384677,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Filters","depth":9,"bounds":{"left":0.1299867,"top":0.12849163,"width":0.3537234,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Filters","depth":11,"bounds":{"left":0.13796543,"top":0.12968874,"width":0.013630319,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":10,"bounds":{"left":0.1299867,"top":0.16480447,"width":0.013297873,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Recipient, description…","depth":10,"bounds":{"left":0.1299867,"top":0.18036711,"width":0.3537234,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Source","depth":10,"bounds":{"left":0.1299867,"top":0.22067039,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Status","depth":10,"bounds":{"left":0.21941489,"top":0.22067039,"width":0.012466756,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Type","depth":10,"bounds":{"left":0.30884308,"top":0.22067039,"width":0.009474734,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tag","depth":10,"bounds":{"left":0.39827126,"top":0.22067039,"width":0.006981383,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"From date","depth":10,"bounds":{"left":0.1299867,"top":0.27414206,"width":0.019448139,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dd","depth":11,"bounds":{"left":0.13530585,"top":0.2980846,"width":0.0056515955,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":10,"bounds":{"left":0.1419548,"top":0.2980846,"width":0.0013297872,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mm","depth":11,"bounds":{"left":0.14428191,"top":0.2980846,"width":0.007978723,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":10,"bounds":{"left":0.15325798,"top":0.2980846,"width":0.0013297872,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yyyy","depth":11,"bounds":{"left":0.15558511,"top":0.2980846,"width":0.009973404,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":10,"bounds":{"left":0.29371676,"top":0.2988827,"width":0.006150266,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"To date","depth":10,"bounds":{"left":0.30884308,"top":0.27414206,"width":0.014295213,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dd","depth":11,"bounds":{"left":0.31416222,"top":0.2980846,"width":0.0056515955,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":10,"bounds":{"left":0.32081118,"top":0.2980846,"width":0.0013297872,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mm","depth":11,"bounds":{"left":0.3231383,"top":0.2980846,"width":0.007978723,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":10,"bounds":{"left":0.33211437,"top":0.2980846,"width":0.0013297872,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yyyy","depth":11,"bounds":{"left":0.33444148,"top":0.2980846,"width":0.009973404,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Calendar","depth":10,"bounds":{"left":0.47257313,"top":0.2988827,"width":0.006150266,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"187","depth":9,"bounds":{"left":0.1299867,"top":0.35953712,"width":0.007978723,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"transaction","depth":9,"bounds":{"left":0.13796543,"top":0.35953712,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"s","depth":9,"bounds":{"left":0.16356383,"top":0.35953712,"width":0.0023271276,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(balance alerts hidden)","depth":9,"bounds":{"left":0.16722074,"top":0.36113328,"width":0.04338431,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"·","depth":9,"bounds":{"left":0.2159242,"top":0.35953712,"width":0.0013297872,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Total:","depth":9,"bounds":{"left":0.22257313,"top":0.35953712,"width":0.013131649,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10,250.48","depth":9,"bounds":{"left":0.2357048,"top":0.35953712,"width":0.023936171,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EUR","depth":9,"bounds":{"left":0.26097074,"top":0.36113328,"width":0.007978723,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DATE","depth":13,"bounds":{"left":0.1299867,"top":0.40343177,"width":0.011136968,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RECIPIENT","depth":13,"bounds":{"left":0.2017952,"top":0.40343177,"width":0.023105053,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AMOUNT","depth":13,"bounds":{"left":0.3324468,"top":0.40343177,"width":0.019448139,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TAGS","depth":13,"bounds":{"left":0.38081783,"top":0.40343177,"width":0.011469414,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.4329609,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.4329609,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.43415803,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.4329609,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.4301676,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.43375897,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.4293695,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.43375897,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.4293695,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"RAW DATA","depth":14,"bounds":{"left":0.13131648,"top":0.46727854,"width":0.020944148,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zdraveyte, ot 01/07/2026 vlizat v sila izmeneni Obshti uslovia, a ot 01/09/2026 - izmenena Tarifa na Banka DSK. Poveche na sayta na bankata, sektsia Izvestia.","depth":14,"bounds":{"left":0.13131648,"top":0.4840383,"width":0.33976063,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.5255387,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.5255387,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.52673584,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.5255387,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.52274543,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.5263368,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.5219473,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.5263368,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.5219473,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.55985636,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.55985636,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.56105345,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.55985636,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.55706304,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.5606544,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.55626494,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.5606544,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.55626494,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.59417397,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.59417397,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.5953711,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.59417397,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.5913807,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.5949721,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.5905826,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.5949721,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.5905826,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.62849164,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.62849164,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.62968874,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.62849164,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.6256983,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.6292897,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.6249002,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.6292897,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.6249002,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.66280925,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.66280925,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.6640064,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.66280925,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.66001594,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.66360736,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.6592179,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.66360736,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.6592179,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.6971269,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.6971269,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.698324,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.6971269,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.6943336,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.697925,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.6935355,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.697925,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.6935355,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.73144454,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.73144454,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.73264164,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.73144454,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.7286512,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.73224264,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.7278532,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.73224264,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.7278532,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.76576215,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.76576215,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.7669593,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.76576215,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.7629689,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.76656026,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.7621708,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.76656026,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.7621708,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.8000798,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.8000798,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.8012769,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.8000798,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.7972865,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.80087787,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.7964884,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.80087787,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.7964884,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.83439744,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.83439744,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.8355946,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.83439744,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.8316041,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.83519554,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.8308061,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.83519554,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.8308061,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.8687151,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.8687151,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.8699122,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.8687151,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.8659218,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.86951315,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.8651237,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.86951315,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.8651237,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.9030327,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.9030327,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.9042298,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.9030327,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.9002394,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.9038308,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.89944136,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.9038308,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.89944136,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.93735033,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.93735033,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.9385475,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.93735033,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.9345571,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.93814844,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.933759,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.93814844,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.933759,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":0.971668,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":0.971668,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":0.9728651,"width":0.004654255,"height":0.011173184},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":0.971668,"width":0.004155585,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":0.9688747,"width":0.021775266,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":0.9724661,"width":0.009807181,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":0.9680766,"width":0.020944148,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":0.9724661,"width":0.00831117,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":0.9680766,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":1.0,"width":0.0039893617,"height":-0.0059856176},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":1.0,"width":0.0039893617,"height":-0.0059856176},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":1.0,"width":0.004654255,"height":-0.0071827173},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":1.0,"width":0.004155585,"height":-0.0059856176},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":1.0,"width":0.021775266,"height":-0.0031923056},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":1.0,"width":0.009807181,"height":-0.006783724},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":1.0,"width":0.020944148,"height":-0.0023941994},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":1.0,"width":0.00831117,"height":-0.006783724},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":1.0,"width":0.008643617,"height":-0.0023941994},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":1.0,"width":0.0039893617,"height":-0.04030323},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":1.0,"width":0.0039893617,"height":-0.04030323},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":1.0,"width":0.004654255,"height":-0.04150045},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":1.0,"width":0.004155585,"height":-0.04030323},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":1.0,"width":0.021775266,"height":-0.03750992},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":1.0,"width":0.009807181,"height":-0.041101336},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":1.0,"width":0.020944148,"height":-0.03671193},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":1.0,"width":0.00831117,"height":-0.041101336},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":1.0,"width":0.008643617,"height":-0.03671193},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.1299867,"top":1.0,"width":0.0039893617,"height":-0.07462096},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"bounds":{"left":0.2017952,"top":1.0,"width":0.0039893617,"height":-0.07462096},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"bounds":{"left":0.20711437,"top":1.0,"width":0.004654255,"height":-0.07581806},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"bounds":{"left":0.3324468,"top":1.0,"width":0.004155585,"height":-0.07462096},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"bounds":{"left":0.40757978,"top":1.0,"width":0.021775266,"height":-0.07182765},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"bounds":{"left":0.4162234,"top":1.0,"width":0.009807181,"height":-0.07541895},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"bounds":{"left":0.43068483,"top":1.0,"width":0.020944148,"height":-0.071029544},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"bounds":{"left":0.4396609,"top":1.0,"width":0.00831117,"height":-0.07541895},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"bounds":{"left":0.45295876,"top":1.0,"width":0.008643617,"height":-0.071029544},"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"—","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"09 May 2026, 15:02","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"VIADENTAL, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"124.00 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"09 May 2026, 14:54","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"VIADENTAL, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"117.00 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"08 May 2026, 19:32","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"LIDL BALGARIYA EOOD, SOFIYA, BGR","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"67.81 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"08 May 2026, 18:45","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sinsay, Sofia, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5.02 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"08 May 2026, 18:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TEMA RETAIL BG EOOD, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15.46 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"08 May 2026, 18:07","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DM BULGARIA EOOD, SOFIYA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"9.04 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"08 May 2026, 10:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DSK ATM, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"200.00 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"07 May 2026, 09:02","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CBA EKO MARKET, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5.51 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"06 May 2026, 19:02","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CBA ECO MARKET, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5.93 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"06 May 2026, 18:40","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FANTASTICO GROUP LTD, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"13.02 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"06 May 2026, 17:19","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TAXI 065, SOFIA, BG","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"17.00 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"06 May 2026, 13:16","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lagardere Travel R","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"5.49 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"05 May 2026, 18:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dennikn","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"7.99 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Send","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Send","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Skip","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Delete","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"05 May 2026, 18:05","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Dr","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Raw data","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1.28 EUR","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8953295336839521602
|
1873547281763017699
|
visual_change
|
accessibility
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (2) - [EMAIL] - Gmail
Inbox (2) - [EMAIL] - Gmail
(25) Quora
(25) Quora
Location Logger
Location Logger
Finance Hub
Finance Hub
Finance Hub
Finance Hub
Close tab
Select: transactions - db - Adminer
Select: transactions - db - Adminer
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE
Stop Losing Notes: Pick A Cross-Device App That Syncs | AFFiNE
VIVACOM
VIVACOM
Смартфони с Unlimited план до 120 € отстъпка | Vivacom
Смартфони с Unlimited план до 120 € отстъпка | Vivacom
VIVACOM
VIVACOM
Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom
Смартфон SAMSUNG GALAXY A57 5G 256GB | Vivacom
Claude Code | Claude Platform
Claude Code | Claude Platform
Claude
Claude
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Finance Hub
Finance Hub
187
transaction
s
Payments
Payments
Upload CSV
Upload CSV
Refresh
Display settings
Sign out
Filters
Filters
Search
Recipient, description…
Source
Status
Type
Tag
From date
dd
/
mm
/
yyyy
Calendar
To date
dd
/
mm
/
yyyy
Calendar
187
transaction
s
(balance alerts hidden)
·
Total:
10,250.48
EUR
DATE
RECIPIENT
AMOUNT
TAGS
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
RAW DATA
Zdraveyte, ot 01/07/2026 vlizat v sila izmeneni Obshti uslovia, a ot 01/09/2026 - izmenena Tarifa na Banka DSK. Poveche na sayta na bankata, sektsia Izvestia.
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
—
—
Raw data
—
Send
Send
Skip
Skip
Delete
09 May 2026, 15:02
VIADENTAL, SOFIA, BG
Raw data
124.00 EUR
Send
Send
Skip
Skip
Delete
09 May 2026, 14:54
VIADENTAL, SOFIA, BG
Raw data
117.00 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 19:32
LIDL BALGARIYA EOOD, SOFIYA, BGR
Raw data
67.81 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:45
Sinsay, Sofia, BG
Raw data
5.02 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:35
TEMA RETAIL BG EOOD, SOFIA, BG
Raw data
15.46 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 18:07
DM BULGARIA EOOD, SOFIYA, BG
Raw data
9.04 EUR
Send
Send
Skip
Skip
Delete
08 May 2026, 10:00
DSK ATM, SOFIA, BG
Raw data
200.00 EUR
Send
Send
Skip
Skip
Delete
07 May 2026, 09:02
CBA EKO MARKET, SOFIA, BG
Raw data
5.51 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 19:02
CBA ECO MARKET, SOFIA, BG
Raw data
5.93 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 18:40
FANTASTICO GROUP LTD, SOFIA, BG
Raw data
13.02 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 17:19
TAXI 065, SOFIA, BG
Raw data
17.00 EUR
Send
Send
Skip
Skip
Delete
06 May 2026, 13:16
Lagardere Travel R
Raw data
5.49 EUR
Send
Send
Skip
Skip
Delete
05 May 2026, 18:55
dennikn
Raw data
7.99 EUR
Send
Send
Skip
Skip
Delete
05 May 2026, 18:05
Dr
Raw data
1.28 EUR...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
22014
|
963
|
21
|
2026-05-12T06:45:26.852552+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778568326852_m2.jpg...
|
iTerm2
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
‹ $0 lil | Daily -Platform • now* 100% C &• Tu ‹ $0 lil | Daily -Platform • now* 100% C &• Tue 12 May 9:45:26ervice lest.oneRematchActivity(foller.pnpC) Hubsoot/Service.onpС)MatchacuivityermData.pngSautomatedkeporc.••• • < → сNew TabJy 20820 es reindex stream mode4 [JY-20725] [HubSpot] Optimise CIJY-20725 add HS rate limit har XPipelines - jiminny/app( Pull requests • jiminny/app[JY-20773] User Pilot not receivin• Jy-20773 fix user pilot trackina ofi# [JY-20776] Automated report - serA TypeError: Leaque\Flysystem\FilesTypeError: Leaque|Flvsystem|FilesPlatform Sprint 3 Q2 - Platform Te:— New Tabl• github.comActivityLateMoreJiminny... v# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofa-office# support# thank-yous# the_people_of_jimi..^? Direct messages% Galya Dimitrovafe Aneliva Angelovaf Petko KashinskiStefka Stoyanovaa Vasil VasilevNikolay Ivanov(3) Aneliva Angelova…, Covan TanevVesLukas Kovali…::: Apps8 Toast4" Jira Cloud(m) Google Cale...$ 2.CRSMIEcOsIeoeareIoDod€ BadKevwordsQuervExcer( ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpE DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen® ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.php@ Handler.php(@ HttnRodDoauoctEycontinA UttnRodDQ Describe what you are looking forGalya DimitrovaMessagese FilesF UntitledLukas Kovallk 9.39 AM•може и за interest tracking да намправяедин тикетGalya Dimitrova 9:39 AMи там ли не паботи.Lukas Kovalik 9:39 AMПетко ми писа че си пристига нещо мулипсваше така че трябва да видя каквода добавя в payloadне знам още какъв точно е проблемGalva Dimitrova 9:41 AMаха, ако можеш направо сега ла го.глелаш че повали различни пооблеми неработи пялата схема с нотификациите и.Плануат от както сме пуснали фичіла. Ивсеки ден след кой го клика за да давамрепорти на CS и много ми се иска даполкапаме автомативациятаLukas Kovallk 9:41 AMдобреGalva Dimitrova 9•42 AMто пьово і ланхат имаха оъг и ги чаках.елна селмица ла го фикснаїMessage Galya Dimitrova In a meeting •+ AalJY-20725 add HS rate limit handling on activities rematching #12066LakyLak wants to merge 4 commits into master from JY-20725-handle-HS-search-rate-limit LMeasuresv U security hotspotsv 94.8% Coverage on New Code0.0% Duplication on New CodeSee analysis details on SonarQube CloudLakyLak commented 14 hours agoAuthor)..aclaude@• 1|claude Bot commented 14 hours ago • edited -Claude finished @LakyLak's task in 2m Os —— View jobCoce keview• Gatherina context• Reading changed files•Analyzing code qualityPosting review feedbackThe previous round of review feedback has been largely addressed — good work. Most of the earlier issues (keyamina visibility double-clamnina test coverade failed() comment) have been resolved. A few remaining itemebelow.Still Open from Previous Review1. batchReadObiects() _ 429s now silently converted to CrnExcentionThis is the original issue #1 but has gotten slightly worse. The try/catch in batchReadObiects() (line 316) catches al\Throwable and passes it to handleBatchError() (line 419), which unconditionally wraps it in CrmException :throw new CrmException("Failed to batch fetch {SobiectTvpe}: " , SerrorMessage):Thic moane a 120 from tho hatch ADI ic.• Not detected as a rate-limit event.• Never sets the Redis circuit-breaker• Thrown as CrmFycention instead of Ratel imitFycention...
|
NULL
|
8953284701883988269
|
NULL
|
click
|
ocr
|
NULL
|
‹ $0 lil | Daily -Platform • now* 100% C &• Tu ‹ $0 lil | Daily -Platform • now* 100% C &• Tue 12 May 9:45:26ervice lest.oneRematchActivity(foller.pnpC) Hubsoot/Service.onpС)MatchacuivityermData.pngSautomatedkeporc.••• • < → сNew TabJy 20820 es reindex stream mode4 [JY-20725] [HubSpot] Optimise CIJY-20725 add HS rate limit har XPipelines - jiminny/app( Pull requests • jiminny/app[JY-20773] User Pilot not receivin• Jy-20773 fix user pilot trackina ofi# [JY-20776] Automated report - serA TypeError: Leaque\Flysystem\FilesTypeError: Leaque|Flvsystem|FilesPlatform Sprint 3 Q2 - Platform Te:— New Tabl• github.comActivityLateMoreJiminny... v# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofa-office# support# thank-yous# the_people_of_jimi..^? Direct messages% Galya Dimitrovafe Aneliva Angelovaf Petko KashinskiStefka Stoyanovaa Vasil VasilevNikolay Ivanov(3) Aneliva Angelova…, Covan TanevVesLukas Kovali…::: Apps8 Toast4" Jira Cloud(m) Google Cale...$ 2.CRSMIEcOsIeoeareIoDod€ BadKevwordsQuervExcer( ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpE DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen® ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.php@ Handler.php(@ HttnRodDoauoctEycontinA UttnRodDQ Describe what you are looking forGalya DimitrovaMessagese FilesF UntitledLukas Kovallk 9.39 AM•може и за interest tracking да намправяедин тикетGalya Dimitrova 9:39 AMи там ли не паботи.Lukas Kovalik 9:39 AMПетко ми писа че си пристига нещо мулипсваше така че трябва да видя каквода добавя в payloadне знам още какъв точно е проблемGalva Dimitrova 9:41 AMаха, ако можеш направо сега ла го.глелаш че повали различни пооблеми неработи пялата схема с нотификациите и.Плануат от както сме пуснали фичіла. Ивсеки ден след кой го клика за да давамрепорти на CS и много ми се иска даполкапаме автомативациятаLukas Kovallk 9:41 AMдобреGalva Dimitrova 9•42 AMто пьово і ланхат имаха оъг и ги чаках.елна селмица ла го фикснаїMessage Galya Dimitrova In a meeting •+ AalJY-20725 add HS rate limit handling on activities rematching #12066LakyLak wants to merge 4 commits into master from JY-20725-handle-HS-search-rate-limit LMeasuresv U security hotspotsv 94.8% Coverage on New Code0.0% Duplication on New CodeSee analysis details on SonarQube CloudLakyLak commented 14 hours agoAuthor)..aclaude@• 1|claude Bot commented 14 hours ago • edited -Claude finished @LakyLak's task in 2m Os —— View jobCoce keview• Gatherina context• Reading changed files•Analyzing code qualityPosting review feedbackThe previous round of review feedback has been largely addressed — good work. Most of the earlier issues (keyamina visibility double-clamnina test coverade failed() comment) have been resolved. A few remaining itemebelow.Still Open from Previous Review1. batchReadObiects() _ 429s now silently converted to CrnExcentionThis is the original issue #1 but has gotten slightly worse. The try/catch in batchReadObiects() (line 316) catches al\Throwable and passes it to handleBatchError() (line 419), which unconditionally wraps it in CrmException :throw new CrmException("Failed to batch fetch {SobiectTvpe}: " , SerrorMessage):Thic moane a 120 from tho hatch ADI ic.• Not detected as a rate-limit event.• Never sets the Redis circuit-breaker• Thrown as CrmFycention instead of Ratel imitFycention...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
629
|
24
|
54
|
2026-05-07T07:21:05.187883+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778138465187_m2.jpg...
|
Firefox
|
Moje platby | Denník N — Personal
|
True
|
predplatne.dennikn.sk/payments/payments/my
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
Home | Hostinger
Home | Hostinger
Login – Nginx Proxy Manager
Login – Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Moje platby | Denník N
Moje platby | Denník N
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Preskočiť hlavnú navigáciu
Preskočiť hlavnú navigáciu
Denník N
Denník N
Denník N
[EMAIL]
[EMAIL]
Predplatné
Predplatné
Platby
Platby
Newslettre
Newslettre
Témy a autori
Témy a autori
Adresa, faktúry
Adresa, faktúry
Nastavenia
Nastavenia
Odhlásiť
Odhlásiť
Predĺžiť predplatné
Predĺžiť predplatné
Zmeniť predplatné / Upgrade
Zmeniť predplatné / Upgrade
Moje platby
Moje platby
Máte zapnuté automatické obnovovanie predplatného, najbližšia platba v hodnote
7,99 €
prebehne
2. júna 2026, 17:59:50
.
Spôsob platby / Dňa
Variabilný symbol
Cena
Opakovaná platba
Doklady
TrustPay Recurrent
5. mája 2026, 17:55:13
6447050049
7,99 €
Zastaviť opakovanie
Zastaviť opakovanie
Zmeniť spôsob platby
Zmeniť spôsob platby
Bez faktúry
TrustPay Recurrent
7. apríla 2026, 17:55:25
2894942512
7,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
10. marca 2026, 17:55:36
3017941618
7,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
10. februára 2026, 17:55:24
2465503593
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
13. januára 2026, 17:55:27
9076033887
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
16. decembra 2025, 17:55:26
1338293786
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
25. septembra 2025, 17:59:50
1584783519
1,00 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay
15. mája 2025, 19:52:30
3034122509
10,90 €
Faktúra už nedostupná
Apple AppStore
23. februára 2025, 6:36:33
7959379729
7,99 €
Neúspešné opakovanie
Faktúra už nedostupná
Google Play Billing
2. februára 2024, 22:02:39
8549020323
6,00 €
Zastavené Vami
Faktúra už nedostupná
CardPay
2. októbra 2023, 18:47:41
3443090468
5,99 €
Faktúra už nedostupná
CardPay
22. júna 2023, 17:21:19
8011050988
5,99 €
Faktúra už nedostupná
CardPay
6. apríla 2022, 14:16:34
2841552990
47,90 €
Faktúra už nedostupná
CardPay
21. marca 2022, 16:50:06
7728760320
4,99 €
Faktúra už nedostupná
CardPay
19. februára 2022, 21:59:22
0334292059
4,99 €
Faktúra už nedostupná
CardPay
3. januára 2022, 10:46:12
3627698035
4,99 €
Faktúra už nedostupná
CardPay
3. decembra 2021, 16:12:06
9792815364
4,99 €
Faktúra už nedostupná
CardPay
29. októbra 2021, 6:47:26
4348137407
4,99 €
Faktúra už nedostupná
CardPay
1. augusta 2021, 8:51:45
0334512449
4,99 €
Faktúra už nedostupná
CardPay
24. júna 2021, 9:26:56
2393572318
4,99 €
Faktúra už nedostupná
CardPay
23. mája 2021, 7:32:29
1901226579
4,99 €
Faktúra už nedostupná
ComfortPay
13. apríla 2021, 8:45:17
9201830644
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
15. marca 2021, 8:53:25
6545055245
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
6. februára 2021, 4:00:29
8462417603
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
8. januára 2021, 4:12:51
6700989654
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
16. septembra 2020, 13:18:28
7265282635
6,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
16. augusta 2020, 8:37:44
5094670358
6,99 €
Automaticky predĺžená
Faktúra už nedostupná
CardPay
11. júna 2020, 6:49:27
6571622349
6,99 €
Faktúra už nedostupná
CardPay
28. februára 2020, 13:07:55
4354256366
8,90 €
Faktúra už nedostupná
CardPay
20. marca 2019, 6:27:23
3826723272
4,99 €
Faktúra už nedostupná
CardPay
8. januára 2019, 20:28:39
6310498424
4,99 €
Faktúra už nedostupná
CardPay
15. novembra 2018, 8:22:57
4318141703
4,99 €
Faktúra už nedostupná
CardPay
2. októbra 2018, 7:36:23
4641721018
4,99 €
Faktúra už nedostupná
CardPay
25. augusta 2018, 12:00:13
6261705412
4,99 €
Faktúra už nedostupná
CardPay
10. mája 2018, 8:56:13
5546365553
4,99 €
Faktúra už nedostupná
CardPay
2. apríla 2018, 8:35:31
0226465178
8,90 €
Faktúra už nedostupná
CardPay
27. februára 2018, 10:43:33
1775763012
4,99 €
Faktúra už nedostupná
CardPay
25. januára 2018, 8:57:37
6115354589
4,99 €
Faktúra už nedostupná
ComfortPay
5. apríla 2017, 9:30:28
2869448125
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
5. marca 2017, 9:30:26
3670958919
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
2. februára 2017, 9:30:35
7772912131
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
2. januára 2017, 9:38:35
6528317880
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
CardPay
30. novembra 2016, 11:50:08
9301122119
4,99 €
Faktúra už nedostupná
CardPay
13. októbra 2016, 11:53:04
4023624971
4,99 €
Faktúra už nedostupná
CardPay
29. júla 2016, 12:10:10
5017533333
4,90 €
Faktúra už nedostupná
CardPay
25. mája 2016, 8:45:24
4029743302
4,90 €
Faktúra už nedostupná
ComfortPay
2. januára 2016, 12:00:15
2095827927
4,90 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
2. decembra 2015, 12:00:09
5822485598
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
1. novembra 2015, 12:00:08
8675601223
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
1. októbra 2015, 12:00:35
0647240525
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
31. augusta 2015, 12:00:35
0001995608
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
31. júla 2015, 12:00:17
8721043708
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
30. júna 2015, 12:11:00
9738050132
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
Moje objednávky
Moje objednávky
Nemáte žiadne objednávky.
Navštívte náš eshop.
Navštívte náš eshop.
Vrátené platby
Vrátené platby
Nemáte žiadne vrátené platby
Kliknite sem
Kliknite sem
, ak máte otázku alebo potrebujete poradiť.
O Denníku N
O Denníku N
Redakcia
Redakcia
Inzercia
Inzercia
Hospodárenie
Hospodárenie
Diskusie a podujatia
Diskusie a podujatia
Logo
Logo
Kontakt
Kontakt
VOP
VOP
Ochrana údajov
Ochrana údajov
Instagram
Instagram
YouTube
YouTube
Facebook
Facebook
Bluesky
Bluesky
Threads
Threads
X
...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.080784574,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Home | Hostinger","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home | Hostinger","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.03025266,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login – Nginx Proxy Manager","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login – Nginx Proxy Manager","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.05069814,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: archive.db","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.040724736,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.03756649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.11469415,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Moje platby | Denník N","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Moje platby | Denník N","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.0390625,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.10139628,"top":0.32083002,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.34796488,"width":0.108211435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Preskočiť hlavnú navigáciu","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Preskočiť hlavnú navigáciu","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Denník N","depth":8,"bounds":{"left":0.113696806,"top":0.0518755,"width":0.062832445,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"Denník N","depth":9,"bounds":{"left":0.113696806,"top":0.0518755,"width":0.062832445,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Denník N","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"kovaliklukas@gmail.com","depth":8,"bounds":{"left":0.92852396,"top":0.0518755,"width":0.07147604,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"kovaliklukas@gmail.com","depth":9,"bounds":{"left":0.9338431,"top":0.06344773,"width":0.060837764,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Predplatné","depth":10,"bounds":{"left":0.39361703,"top":0.09417398,"width":0.032579787,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Predplatné","depth":11,"bounds":{"left":0.3962766,"top":0.10574621,"width":0.027260639,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platby","depth":10,"bounds":{"left":0.4261968,"top":0.09417398,"width":0.02144282,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platby","depth":11,"bounds":{"left":0.42918882,"top":0.10574621,"width":0.015791224,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Newslettre","depth":10,"bounds":{"left":0.4476396,"top":0.09417398,"width":0.03307846,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Newslettre","depth":11,"bounds":{"left":0.45063165,"top":0.10574621,"width":0.027426861,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Témy a autori","depth":10,"bounds":{"left":0.48071808,"top":0.09417398,"width":0.039727394,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Témy a autori","depth":11,"bounds":{"left":0.4837101,"top":0.10574621,"width":0.034075797,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Adresa, faktúry","depth":10,"bounds":{"left":0.52044547,"top":0.09417398,"width":0.044049203,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Adresa, faktúry","depth":11,"bounds":{"left":0.5234375,"top":0.10574621,"width":0.038397606,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Nastavenia","depth":10,"bounds":{"left":0.56449467,"top":0.09417398,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Nastavenia","depth":11,"bounds":{"left":0.5674867,"top":0.10574621,"width":0.027925532,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Odhlásiť","depth":10,"bounds":{"left":0.5980718,"top":0.09417398,"width":0.02642952,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Odhlásiť","depth":11,"bounds":{"left":0.60106385,"top":0.10574621,"width":0.020777926,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Predĺžiť predplatné","depth":11,"bounds":{"left":0.57546544,"top":0.13647246,"width":0.059840426,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.578125,"top":0.14884278,"width":0.005319149,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Predĺžiť predplatné","depth":12,"bounds":{"left":0.5834442,"top":0.14804469,"width":0.04920213,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Zmeniť predplatné / Upgrade","depth":11,"bounds":{"left":0.63663566,"top":0.13647246,"width":0.08344415,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.6392952,"top":0.14884278,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zmeniť predplatné / Upgrade","depth":12,"bounds":{"left":0.64328456,"top":0.14804469,"width":0.07413564,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Moje platby","depth":8,"bounds":{"left":0.39361703,"top":0.20670392,"width":0.32646278,"height":0.03152434},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Moje platby","depth":9,"bounds":{"left":0.39361703,"top":0.20510775,"width":0.06499335,"height":0.03471668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Máte zapnuté automatické obnovovanie predplatného, najbližšia platba v hodnote","depth":11,"bounds":{"left":0.42869017,"top":0.27972865,"width":0.17154256,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,99 €","depth":12,"bounds":{"left":0.6002327,"top":0.2793296,"width":0.012965426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prebehne","depth":11,"bounds":{"left":0.61319816,"top":0.27972865,"width":0.022273935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. júna 2026, 17:59:50","depth":12,"bounds":{"left":0.63547206,"top":0.2793296,"width":0.048204787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":11,"bounds":{"left":0.68367684,"top":0.27972865,"width":0.0013297872,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Spôsob platby / Dňa","depth":13,"bounds":{"left":0.39361703,"top":0.3272147,"width":0.04504654,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Variabilný symbol","depth":13,"bounds":{"left":0.47772607,"top":0.3272147,"width":0.03873005,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cena","depth":13,"bounds":{"left":0.5403923,"top":0.3272147,"width":0.011469414,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Opakovaná platba","depth":15,"bounds":{"left":0.5787899,"top":0.3272147,"width":0.040226065,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Doklady","depth":15,"bounds":{"left":0.6740359,"top":0.3272147,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.35634476,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. mája 2026, 17:55:13","depth":12,"bounds":{"left":0.39361703,"top":0.37230647,"width":0.047539894,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6447050049","depth":12,"bounds":{"left":0.47772607,"top":0.35634476,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.35634476,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Zastaviť opakovanie","depth":14,"bounds":{"left":0.5618351,"top":0.3547486,"width":0.07413564,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.5789561,"top":0.36153233,"width":0.0029920214,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastaviť opakovanie","depth":15,"bounds":{"left":0.58194816,"top":0.36113328,"width":0.036901597,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Zmeniť spôsob platby","depth":14,"bounds":{"left":0.5618351,"top":0.38268158,"width":0.07413564,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Zmeniť spôsob platby","depth":15,"bounds":{"left":0.5794548,"top":0.38906625,"width":0.038896278,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bez faktúry","depth":15,"bounds":{"left":0.67303854,"top":0.36113328,"width":0.019946808,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.42657623,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7. apríla 2026, 17:55:25","depth":12,"bounds":{"left":0.39361703,"top":0.4425379,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2894942512","depth":12,"bounds":{"left":0.47772607,"top":0.42657623,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.42657623,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.42897046,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.43176377,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.43136472,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.47126895,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. marca 2026, 17:55:36","depth":12,"bounds":{"left":0.39361703,"top":0.48723066,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3017941618","depth":12,"bounds":{"left":0.47772607,"top":0.47126895,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.47126895,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.4736632,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.4764565,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.47605747,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.5159617,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. februára 2026, 17:55:24","depth":12,"bounds":{"left":0.39361703,"top":0.5319234,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2465503593","depth":12,"bounds":{"left":0.47772607,"top":0.5159617,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.5159617,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.51835597,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.5211492,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.5207502,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.5606544,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13. januára 2026, 17:55:27","depth":12,"bounds":{"left":0.39361703,"top":0.5766161,"width":0.055352394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9076033887","depth":12,"bounds":{"left":0.47772607,"top":0.5606544,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.5606544,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.56304866,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.565842,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.5654429,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.60534716,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. decembra 2025, 17:55:26","depth":12,"bounds":{"left":0.39361703,"top":0.62130886,"width":0.061170213,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1338293786","depth":12,"bounds":{"left":0.47772607,"top":0.60534716,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.60534716,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.6077414,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.6105347,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.6101357,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay Recurrent","depth":12,"bounds":{"left":0.39361703,"top":0.6500399,"width":0.039727394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. septembra 2025, 17:59:50","depth":12,"bounds":{"left":0.39361703,"top":0.6660016,"width":0.0625,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1584783519","depth":12,"bounds":{"left":0.47772607,"top":0.6500399,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,00 €","depth":12,"bounds":{"left":0.53889626,"top":0.6500399,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"bounds":{"left":0.57978725,"top":0.6524342,"width":0.038231384,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.6552275,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.6548284,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TrustPay","depth":12,"bounds":{"left":0.39361703,"top":0.69473267,"width":0.017952127,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. mája 2025, 19:52:30","depth":12,"bounds":{"left":0.39361703,"top":0.7106943,"width":0.050199468,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3034122509","depth":12,"bounds":{"left":0.47772607,"top":0.69473267,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10,90 €","depth":12,"bounds":{"left":0.53640294,"top":0.69473267,"width":0.015458777,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.6999202,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.6995211,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Apple AppStore","depth":12,"bounds":{"left":0.39361703,"top":0.73942536,"width":0.03307846,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. februára 2025, 6:36:33","depth":12,"bounds":{"left":0.39361703,"top":0.75538707,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7959379729","depth":12,"bounds":{"left":0.47772607,"top":0.73942536,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.73942536,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Neúspešné opakovanie","depth":14,"bounds":{"left":0.5794548,"top":0.7418196,"width":0.038896278,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.74461293,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.7442139,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Google Play Billing","depth":12,"bounds":{"left":0.39361703,"top":0.7841181,"width":0.0390625,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2024, 22:02:39","depth":12,"bounds":{"left":0.39361703,"top":0.8000798,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8549020323","depth":12,"bounds":{"left":0.47772607,"top":0.7841181,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6,00 €","depth":12,"bounds":{"left":0.53889626,"top":0.7841181,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"bounds":{"left":0.58577126,"top":0.7865124,"width":0.026263298,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.7893057,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.78890663,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":0.8288109,"width":0.017952127,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2023, 18:47:41","depth":12,"bounds":{"left":0.39361703,"top":0.8447725,"width":0.053523935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3443090468","depth":12,"bounds":{"left":0.47772607,"top":0.8288109,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.8288109,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.8339984,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.8335994,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":0.87350357,"width":0.017952127,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22. júna 2023, 17:21:19","depth":12,"bounds":{"left":0.39361703,"top":0.8894653,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8011050988","depth":12,"bounds":{"left":0.47772607,"top":0.87350357,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.87350357,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.87869114,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.8782921,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":0.9181963,"width":0.017952127,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6. apríla 2022, 14:16:34","depth":12,"bounds":{"left":0.39361703,"top":0.934158,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2841552990","depth":12,"bounds":{"left":0.47772607,"top":0.9181963,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"47,90 €","depth":12,"bounds":{"left":0.53640294,"top":0.9181963,"width":0.015458777,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.9233839,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.92298484,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":0.9628891,"width":0.017952127,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21. marca 2022, 16:50:06","depth":12,"bounds":{"left":0.39361703,"top":0.9788508,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7728760320","depth":12,"bounds":{"left":0.47772607,"top":0.9628891,"width":0.025930852,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"bounds":{"left":0.53889626,"top":0.9628891,"width":0.012965426,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":0.9680766,"width":0.0039893617,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":0.9676776,"width":0.042220745,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":1.0,"width":0.017952127,"height":-0.00758183},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19. februára 2022, 21:59:22","depth":12,"bounds":{"left":0.39361703,"top":1.0,"width":0.057513297,"height":-0.023543477},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0334292059","depth":12,"bounds":{"left":0.47772607,"top":1.0,"width":0.025930852,"height":-0.00758183},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"bounds":{"left":0.53889626,"top":1.0,"width":0.012965426,"height":-0.00758183},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":1.0,"width":0.0039893617,"height":-0.0127693415},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":1.0,"width":0.042220745,"height":-0.012370348},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":1.0,"width":0.017952127,"height":-0.052274585},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. januára 2022, 10:46:12","depth":12,"bounds":{"left":0.39361703,"top":1.0,"width":0.05285904,"height":-0.06823623},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3627698035","depth":12,"bounds":{"left":0.47772607,"top":1.0,"width":0.025930852,"height":-0.052274585},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"bounds":{"left":0.53889626,"top":1.0,"width":0.012965426,"height":-0.052274585},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.6599069,"top":1.0,"width":0.0039893617,"height":-0.057462096},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"bounds":{"left":0.66389626,"top":1.0,"width":0.042220745,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"bounds":{"left":0.39361703,"top":1.0,"width":0.017952127,"height":-0.09696722},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. decembra 2021, 16:12:06","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9792815364","depth":12,"bounds":{"left":0.47772607,"top":1.0,"width":0.025930852,"height":-0.09696722},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"bounds":{"left":0.53889626,"top":1.0,"width":0.012965426,"height":-0.09696722},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. októbra 2021, 6:47:26","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4348137407","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. augusta 2021, 8:51:45","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0334512449","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"24. júna 2021, 9:26:56","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2393572318","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. mája 2021, 7:32:29","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1901226579","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13. apríla 2021, 8:45:17","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9201830644","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. marca 2021, 8:53:25","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6545055245","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6. februára 2021, 4:00:29","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8462417603","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2021, 4:12:51","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6700989654","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. septembra 2020, 13:18:28","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7265282635","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. augusta 2020, 8:37:44","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5094670358","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11. júna 2020, 6:49:27","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6571622349","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28. februára 2020, 13:07:55","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4354256366","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. marca 2019, 6:27:23","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3826723272","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2019, 20:28:39","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6310498424","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. novembra 2018, 8:22:57","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4318141703","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2018, 7:36:23","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4641721018","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. augusta 2018, 12:00:13","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6261705412","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. mája 2018, 8:56:13","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5546365553","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. apríla 2018, 8:35:31","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0226465178","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27. februára 2018, 10:43:33","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1775763012","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. januára 2018, 8:57:37","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6115354589","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. apríla 2017, 9:30:28","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2869448125","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. marca 2017, 9:30:26","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3670958919","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2017, 9:30:35","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7772912131","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2017, 9:38:35","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6528317880","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. novembra 2016, 11:50:08","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9301122119","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13. októbra 2016, 11:53:04","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4023624971","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,99 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. júla 2016, 12:10:10","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5017533333","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CardPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. mája 2016, 8:45:24","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4029743302","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2016, 12:00:15","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2095827927","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zastavené Vami","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. decembra 2015, 12:00:09","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5822485598","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. novembra 2015, 12:00:08","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8675601223","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. októbra 2015, 12:00:35","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0647240525","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. augusta 2015, 12:00:35","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0001995608","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. júla 2015, 12:00:17","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8721043708","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ComfortPay","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. júna 2015, 12:11:00","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9738050132","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4,90 €","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automaticky predĺžená","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Faktúra už nedostupná","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Moje objednávky","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Moje objednávky","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nemáte žiadne objednávky.","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Navštívte náš eshop.","depth":14,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Navštívte náš eshop.","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Vrátené platby","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Vrátené platby","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nemáte žiadne vrátené platby","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kliknite sem","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kliknite sem","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ak máte otázku alebo potrebujete poradiť.","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"O Denníku N","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"O Denníku N","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Redakcia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Redakcia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inzercia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inzercia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Hospodárenie","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Hospodárenie","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Diskusie a podujatia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Diskusie a podujatia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Logo","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Logo","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kontakt","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kontakt","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"VOP","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"VOP","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Ochrana údajov","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ochrana údajov","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Instagram","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Instagram","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" YouTube","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"YouTube","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Facebook","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Facebook","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Bluesky","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bluesky","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Threads","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Threads","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" X","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8953197443177373815
|
6278429372551177543
|
visual_change
|
accessibility
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
Home | Hostinger
Home | Hostinger
Login – Nginx Proxy Manager
Login – Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Moje platby | Denník N
Moje platby | Denník N
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Preskočiť hlavnú navigáciu
Preskočiť hlavnú navigáciu
Denník N
Denník N
Denník N
[EMAIL]
[EMAIL]
Predplatné
Predplatné
Platby
Platby
Newslettre
Newslettre
Témy a autori
Témy a autori
Adresa, faktúry
Adresa, faktúry
Nastavenia
Nastavenia
Odhlásiť
Odhlásiť
Predĺžiť predplatné
Predĺžiť predplatné
Zmeniť predplatné / Upgrade
Zmeniť predplatné / Upgrade
Moje platby
Moje platby
Máte zapnuté automatické obnovovanie predplatného, najbližšia platba v hodnote
7,99 €
prebehne
2. júna 2026, 17:59:50
.
Spôsob platby / Dňa
Variabilný symbol
Cena
Opakovaná platba
Doklady
TrustPay Recurrent
5. mája 2026, 17:55:13
6447050049
7,99 €
Zastaviť opakovanie
Zastaviť opakovanie
Zmeniť spôsob platby
Zmeniť spôsob platby
Bez faktúry
TrustPay Recurrent
7. apríla 2026, 17:55:25
2894942512
7,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
10. marca 2026, 17:55:36
3017941618
7,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
10. februára 2026, 17:55:24
2465503593
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
13. januára 2026, 17:55:27
9076033887
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
16. decembra 2025, 17:55:26
1338293786
3,99 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay Recurrent
25. septembra 2025, 17:59:50
1584783519
1,00 €
Automaticky predĺžená
Faktúra už nedostupná
TrustPay
15. mája 2025, 19:52:30
3034122509
10,90 €
Faktúra už nedostupná
Apple AppStore
23. februára 2025, 6:36:33
7959379729
7,99 €
Neúspešné opakovanie
Faktúra už nedostupná
Google Play Billing
2. februára 2024, 22:02:39
8549020323
6,00 €
Zastavené Vami
Faktúra už nedostupná
CardPay
2. októbra 2023, 18:47:41
3443090468
5,99 €
Faktúra už nedostupná
CardPay
22. júna 2023, 17:21:19
8011050988
5,99 €
Faktúra už nedostupná
CardPay
6. apríla 2022, 14:16:34
2841552990
47,90 €
Faktúra už nedostupná
CardPay
21. marca 2022, 16:50:06
7728760320
4,99 €
Faktúra už nedostupná
CardPay
19. februára 2022, 21:59:22
0334292059
4,99 €
Faktúra už nedostupná
CardPay
3. januára 2022, 10:46:12
3627698035
4,99 €
Faktúra už nedostupná
CardPay
3. decembra 2021, 16:12:06
9792815364
4,99 €
Faktúra už nedostupná
CardPay
29. októbra 2021, 6:47:26
4348137407
4,99 €
Faktúra už nedostupná
CardPay
1. augusta 2021, 8:51:45
0334512449
4,99 €
Faktúra už nedostupná
CardPay
24. júna 2021, 9:26:56
2393572318
4,99 €
Faktúra už nedostupná
CardPay
23. mája 2021, 7:32:29
1901226579
4,99 €
Faktúra už nedostupná
ComfortPay
13. apríla 2021, 8:45:17
9201830644
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
15. marca 2021, 8:53:25
6545055245
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
6. februára 2021, 4:00:29
8462417603
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
8. januára 2021, 4:12:51
6700989654
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
16. septembra 2020, 13:18:28
7265282635
6,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
16. augusta 2020, 8:37:44
5094670358
6,99 €
Automaticky predĺžená
Faktúra už nedostupná
CardPay
11. júna 2020, 6:49:27
6571622349
6,99 €
Faktúra už nedostupná
CardPay
28. februára 2020, 13:07:55
4354256366
8,90 €
Faktúra už nedostupná
CardPay
20. marca 2019, 6:27:23
3826723272
4,99 €
Faktúra už nedostupná
CardPay
8. januára 2019, 20:28:39
6310498424
4,99 €
Faktúra už nedostupná
CardPay
15. novembra 2018, 8:22:57
4318141703
4,99 €
Faktúra už nedostupná
CardPay
2. októbra 2018, 7:36:23
4641721018
4,99 €
Faktúra už nedostupná
CardPay
25. augusta 2018, 12:00:13
6261705412
4,99 €
Faktúra už nedostupná
CardPay
10. mája 2018, 8:56:13
5546365553
4,99 €
Faktúra už nedostupná
CardPay
2. apríla 2018, 8:35:31
0226465178
8,90 €
Faktúra už nedostupná
CardPay
27. februára 2018, 10:43:33
1775763012
4,99 €
Faktúra už nedostupná
CardPay
25. januára 2018, 8:57:37
6115354589
4,99 €
Faktúra už nedostupná
ComfortPay
5. apríla 2017, 9:30:28
2869448125
4,99 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
5. marca 2017, 9:30:26
3670958919
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
2. februára 2017, 9:30:35
7772912131
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
2. januára 2017, 9:38:35
6528317880
4,99 €
Automaticky predĺžená
Faktúra už nedostupná
CardPay
30. novembra 2016, 11:50:08
9301122119
4,99 €
Faktúra už nedostupná
CardPay
13. októbra 2016, 11:53:04
4023624971
4,99 €
Faktúra už nedostupná
CardPay
29. júla 2016, 12:10:10
5017533333
4,90 €
Faktúra už nedostupná
CardPay
25. mája 2016, 8:45:24
4029743302
4,90 €
Faktúra už nedostupná
ComfortPay
2. januára 2016, 12:00:15
2095827927
4,90 €
Zastavené Vami
Faktúra už nedostupná
ComfortPay
2. decembra 2015, 12:00:09
5822485598
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
1. novembra 2015, 12:00:08
8675601223
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
1. októbra 2015, 12:00:35
0647240525
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
31. augusta 2015, 12:00:35
0001995608
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
31. júla 2015, 12:00:17
8721043708
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
ComfortPay
30. júna 2015, 12:11:00
9738050132
4,90 €
Automaticky predĺžená
Faktúra už nedostupná
Moje objednávky
Moje objednávky
Nemáte žiadne objednávky.
Navštívte náš eshop.
Navštívte náš eshop.
Vrátené platby
Vrátené platby
Nemáte žiadne vrátené platby
Kliknite sem
Kliknite sem
, ak máte otázku alebo potrebujete poradiť.
O Denníku N
O Denníku N
Redakcia
Redakcia
Inzercia
Inzercia
Hospodárenie
Hospodárenie
Diskusie a podujatia
Diskusie a podujatia
Logo
Logo
Kontakt
Kontakt
VOP
VOP
Ochrana údajov
Ochrana údajov
Instagram
Instagram
YouTube
YouTube
Facebook
Facebook
Bluesky
Bluesky
Threads
Threads
X
...
|
628
|
NULL
|
NULL
|
NULL
|
|
24642
|
1026
|
27
|
2026-05-12T09:30:51.684907+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778578251684_m1.jpg...
|
iTerm2
|
-zsh
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelpSupport Daily - in 2h 30 mDOCKER-rw-r--r=--rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---Гw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r--₴811lukaslukaslukas1lukaslukas1lukaslukaslukaslukaslukas1lukaslukaslukaslukaslukaslukaslukas1lukas1lukas1 lukas1lukaslukaslukas1lukaslukaslukaslukaslukaslukaslukas1lukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukasstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaff-zshDEV (-zsh)O $2APP (-zsh)883-zsh|462011May15:57SystemAudio (output)_2026-05-11_12-57-32.mp4462011May15:58462011SystemAudio (output)_2026-05-11_12-57-55.mp4May15:58SystemAudio (output)_2026-05-11_12-58-17.mp4462011May15:59SystemAudio (output)_2026-05-11_12-58-39.mp42145011May15:59462011SystemAudio (output)_2026-05-11_12-59-02.mp4May15:59SystemAudio (output)_2026-05-11_12-59-24.mp4462011May16:00SystemAudio (output)_2026-05-11_12-59-46.mp4462011May16:00SystemAudio (output)_2026-05-11_13-00-08.mp4462011May16:00SystemAudio (output)_2026-05-11_13-00-31.mp4462011May16:01SystemAudio (output)_2026-05-11_13-00-53.mp4462811May16:01System462811May16:02Audio (output)_2026-05-11_13-01-15.mp411SystemAudio (output)_2026-05-11_13-01-38.mp44620May16:02SystemAudio (output)_2026-05-11_13-02-00.mp4462811May16:02SystemAudio (output)_2026-05-11_13-02-22.mp4462011May16:03462011May16:03SystemAudio (output)_2026-05-11_13-02-44.mp4SystemAudio (output)_2026-05-11_13-03-07.mp4462011May16:03System462011Audio (output)_2026-05-11_13-03-29.mp4May16:04SystemAudio (output)_2026-05-11_13-03-52.mp4462011May16:04System Audio (output)_2026-05-11_13-04-14.mp4462011May16:04System Audio (output)_2026-05-11_13-04-36.mp4462011May16:05462011May16:05System Audio (output)_2026-05-11_13-04-58.mp4System Audio (output)_2026-05-11_13-05-20.mp4462011May16:06System Audio (output)_2026-05-11_13-05-42.mp4462011 May16:06SystemAudio (output)_2026-05-11_13-06-05.mp4462011May16:06462811May16:07SystemAudio (output)_2026-05-11_13-06-27.mp4SystemAudio (output)_2026-05-11_13-06-49.mp4462811May16:07System Audio (output)_2026-05-11_13-07-11.mp4462011May16:07SystemAudio (output)_2026-05-11_13-07-34.mp4462811May16:08SystemAudio (output)_2026-05-11_13-07-57.mp4462011May16:08System Audio (output)_2026-05-11_13-08-19.mp4462011May16:09System Audio (output)_2026-05-11_13-08-41.mp4462811May16:09System Audio (output)_2026-05-11_13-09-03.mp4462011May16:09System Audio (output)_2026-05-11_13-09-25.mp4462811May16:10System Audio (output)_2026-05-11_13-09-48.mp4462811May16:10System Audio (output)_2026-05-11_13-10-10.mp4462011May16:10System Audio (output)_2026-05-11_13-10-32.mp4462011May16:11System Audio (output)_2026-05-11_13-10-54.mp4462811May16:11System Audio (output)_2026-05-11_13-11-17.mp4462011May16:12SystemAudio (output)_2026-05-11_13-11-39.mp4462011May16:12System Audio (output)_2026-05-11_13-12-01.mp4462011May16:12System Audio (output)_2026-05-11_13-12-23.mp4462011May16:13System Audio (output)_2026-05-11_13-12-45.mp4462011May16:13System Audio (output)_2026-05-11_13-13-08.mp4462011May16:13System Audio (output)_2026-05-11_13-13-30.mp4462011 May16:14 System Audio (output)_2026-05-11_13-1353.mp484-zsh*5screenpipe"A100% C78•Tue 12 May 12:30:51-zsh181*7...
|
NULL
|
8952599858764542836
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShelllEditViewSessionScriptsProfilesWindowHe iTerm2ShelllEditViewSessionScriptsProfilesWindowHelpSupport Daily - in 2h 30 mDOCKER-rw-r--r=--rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---Гw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r--₴811lukaslukaslukas1lukaslukas1lukaslukaslukaslukaslukas1lukaslukaslukaslukaslukaslukaslukas1lukas1lukas1 lukas1lukaslukaslukas1lukaslukaslukaslukaslukaslukaslukas1lukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukaslukasstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaffstaff-zshDEV (-zsh)O $2APP (-zsh)883-zsh|462011May15:57SystemAudio (output)_2026-05-11_12-57-32.mp4462011May15:58462011SystemAudio (output)_2026-05-11_12-57-55.mp4May15:58SystemAudio (output)_2026-05-11_12-58-17.mp4462011May15:59SystemAudio (output)_2026-05-11_12-58-39.mp42145011May15:59462011SystemAudio (output)_2026-05-11_12-59-02.mp4May15:59SystemAudio (output)_2026-05-11_12-59-24.mp4462011May16:00SystemAudio (output)_2026-05-11_12-59-46.mp4462011May16:00SystemAudio (output)_2026-05-11_13-00-08.mp4462011May16:00SystemAudio (output)_2026-05-11_13-00-31.mp4462011May16:01SystemAudio (output)_2026-05-11_13-00-53.mp4462811May16:01System462811May16:02Audio (output)_2026-05-11_13-01-15.mp411SystemAudio (output)_2026-05-11_13-01-38.mp44620May16:02SystemAudio (output)_2026-05-11_13-02-00.mp4462811May16:02SystemAudio (output)_2026-05-11_13-02-22.mp4462011May16:03462011May16:03SystemAudio (output)_2026-05-11_13-02-44.mp4SystemAudio (output)_2026-05-11_13-03-07.mp4462011May16:03System462011Audio (output)_2026-05-11_13-03-29.mp4May16:04SystemAudio (output)_2026-05-11_13-03-52.mp4462011May16:04System Audio (output)_2026-05-11_13-04-14.mp4462011May16:04System Audio (output)_2026-05-11_13-04-36.mp4462011May16:05462011May16:05System Audio (output)_2026-05-11_13-04-58.mp4System Audio (output)_2026-05-11_13-05-20.mp4462011May16:06System Audio (output)_2026-05-11_13-05-42.mp4462011 May16:06SystemAudio (output)_2026-05-11_13-06-05.mp4462011May16:06462811May16:07SystemAudio (output)_2026-05-11_13-06-27.mp4SystemAudio (output)_2026-05-11_13-06-49.mp4462811May16:07System Audio (output)_2026-05-11_13-07-11.mp4462011May16:07SystemAudio (output)_2026-05-11_13-07-34.mp4462811May16:08SystemAudio (output)_2026-05-11_13-07-57.mp4462011May16:08System Audio (output)_2026-05-11_13-08-19.mp4462011May16:09System Audio (output)_2026-05-11_13-08-41.mp4462811May16:09System Audio (output)_2026-05-11_13-09-03.mp4462011May16:09System Audio (output)_2026-05-11_13-09-25.mp4462811May16:10System Audio (output)_2026-05-11_13-09-48.mp4462811May16:10System Audio (output)_2026-05-11_13-10-10.mp4462011May16:10System Audio (output)_2026-05-11_13-10-32.mp4462011May16:11System Audio (output)_2026-05-11_13-10-54.mp4462811May16:11System Audio (output)_2026-05-11_13-11-17.mp4462011May16:12SystemAudio (output)_2026-05-11_13-11-39.mp4462011May16:12System Audio (output)_2026-05-11_13-12-01.mp4462011May16:12System Audio (output)_2026-05-11_13-12-23.mp4462011May16:13System Audio (output)_2026-05-11_13-12-45.mp4462011May16:13System Audio (output)_2026-05-11_13-13-08.mp4462011May16:13System Audio (output)_2026-05-11_13-13-30.mp4462011 May16:14 System Audio (output)_2026-05-11_13-1353.mp484-zsh*5screenpipe"A100% C78•Tue 12 May 12:30:51-zsh181*7...
|
24639
|
NULL
|
NULL
|
NULL
|
|
7791
|
348
|
13
|
2026-05-08T09:31:37.708075+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778232697708_m2.jpg...
|
Firefox
|
Jy 20493 smart instant nudge pre filtering by niko Jy 20493 smart instant nudge pre filtering by nikolaybiaivanov · Pull Request #12053 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/12053/changes
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -4,16 +4,17 @@445namespace Jiminny\Component\Nudge\Job;5namespace Jiminny\Component\Nudge\Job;667+use Carbon\Carbon;7use Illuminate\Bus\Queueable;8use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Contracts\Queue\ShouldQueue;9-use Illuminate\Database\Eloquent\Builder;10use Illuminate\Foundation\Bus\Dispatchable;10use Illuminate\Foundation\Bus\Dispatchable;11use Illuminate\Queue\InteractsWithQueue;11use Illuminate\Queue\InteractsWithQueue;12use Illuminate\Queue\SerializesModels;12use Illuminate\Queue\SerializesModels;13use Illuminate\Support\Facades\Log;13use Illuminate\Support\Facades\Log;14use Jiminny\Component\Queue\Constants;14use Jiminny\Component\Queue\Constants;15use Jiminny\Models\Activity;15use Jiminny\Models\Activity;16use Jiminny\Models\Activity\Search;16use Jiminny\Models\Activity\Search;17+use Jiminny\Models\Activity\SearchFilter;17use Jiminny\Models\Nudge;18use Jiminny\Models\Nudge;18use Jiminny\Models\Team;19use Jiminny\Models\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20493 smart instant nudge pre filtering by niko</tabTitle>” with “<selection>@@ -4,16 +4,17 @@445namespace Jiminny\Component\Nudge\Job;5namespace Jiminny\Component\Nudge\Job;667+use Carbon\Carbon;7use Illuminate\Bus\Queueable;8use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Contracts\Queue\ShouldQueue;9-use Illuminate\Database\Eloquent\Builder;10use Illuminate\Foundation\Bus\Dispatchable;10use Illuminate\Foundation\Bus\Dispatchable;11use Illuminate\Queue\InteractsWithQueue;11use Illuminate\Queue\InteractsWithQueue;12use Illuminate\Queue\SerializesModels;12use Illuminate\Queue\SerializesModels;13use Illuminate\Support\Facades\Log;13use Illuminate\Support\Facades\Log;14use Jiminny\Component\Queue\Constants;14use Jiminny\Component\Queue\Constants;15use Jiminny\Models\Activity;15use Jiminny\Models\Activity;16use Jiminny\Models\Activity\Search;16use Jiminny\Models\Activity\Search;17+use Jiminny\Models\Activity\SearchFilter;17use Jiminny\Models\Nudge;18use Jiminny\Models\Nudge;18use Jiminny\Models\Team;19use Jiminny\Models\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
This code selection introduces a
pre-filtering mechanism
to the
Jiminny\Component\Nudge\Job
namespace. The primary goal is to optimize performance by validating an activity against search criteria in-memory before dispatching costly background jobs.
Core Functionality Changes
Core Functionality Changes
Selective Loading:
The
handle()
method now eager-loads
user.group
for activities and specifically filters for
FREQ_INSTANTLY
nudges and their associated search filters.
Pre-Dispatch Validation:
Instead of dispatching jobs for every instant nudge, the system now calls
activityMightMatchSearch()
.
Short-Circuiting:
If an activity fails a pre-filter check (e.g., mismatched user ID or duration), the job dispatch is skipped, and the event is logged with the specific mismatch reason.
Performance Metrics:
Adds summary logging at the end of the process to track:
Total searches processed.
Number of skipped dispatches.
The "Skip Rate" percentage.
Implemented Filter Checks
Implemented Filter Checks
The logic includes a
match
expression to validate various activity attributes against
SearchFilter
values:
Identity & Metadata:
Matches for
group_id
,
user_id
,
channel_id
, and
provider_id
.
Temporal & Duration:
* Validates
min_duration
and
max_duration
.
Checks
start_date
and
end_date
using Carbon parsing.
Activity State:
*
Logging:
Determines if an activity is logged to a CRM or is internal via
checkNotLogged
.
Recording:
Uses
checkOnlyRecorded
to filter based on whether a recording exists.
Categorization:
Checks for
category_id
,
stage_id
, and
languages
.
Note:
The language check specifically extracts base codes (e.g.,
en
from
en_GB
) to align with Elasticsearch behavior.
Logic Design
Logic Design
Nullable Returns:
Filter methods return
null
if a match is possible (or cannot be determined), and
false
only when a definitive mismatch is found.
Safe Fallback:
If a filter type is unknown (
default
), the system assumes a match and proceeds to dispatch, ensuring no false negatives.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt
You said what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks
You said
what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks
Expand
Listen
Show more options
Show thinking
Gemini said
Gemini said
Here is a review of the changes provided in the diff, broken down by your requested structure.
1. Review of Changes
1. Review of Changes
This PR refactors the
ProcessNudgeSearchJob
dispatching mechanism within the
handle()
method.
Query Optimization:
Eager loads necessary relationships (
user.group
,
filters
) and scopes the
nudges
relationship at the database level to only fetch
FREQ_INSTANTLY
records.
In-Memory Filtering:
Introduces a pre-filtering mechanism (
activityMightMatchSearch
) that checks the
Activity
against
SearchFilter
criteria before deciding to dispatch a job.
Helper Methods:
Adds 13 private
check*
methods to handle the specific comparison logic for different filter types (e.g., duration, dates, categories, stages).
Observability:
Adds structured logging to record exactly why a nudge was skipped and outputs a summary report (total, skipped, dispatched, skip rate) at the end of the execution.
2. Explanation of Logic
2. Explanation of Logic
The core philosophy of this change is
"veto-based short-circuiting"
.
Previously, the job fetched all searches with instant nudges for a team's users and blindly dispatched a queue job for every single one. The heavy lifting of deciding if the activity actually matched the search was likely deferred to the queue worker and Elasticsearch.
Now, the code evaluates the filters in the current process:
It iterates through each
Search
and its associated
SearchFilter
s.
It routes the filter to a specific
check*
method using a
match
expression based on the filter's property name.
The Veto:
The
check*
methods are designed to return
false
only if there is an absolute mismatch
(e.g., the filter requires a minimum duration of 10, but the activity is 5).
The Pass:
If the filter matches, or if the activity lacks the data needed to make a firm decision (e.g., no stage is assigned, or the filter type is unrecognized), the method returns
null
. This acts as a pass, delegating the final strict evaluation to Elasticsearch later in the pipeline.
If any filter returns
false
, the search is skipped, saving a queue dispatch.
3. Evaluation
3. Evaluation
Overall, this is a
highly effective performance optimization
.
Pros:
It significantly reduces queue bloat. By filtering out obvious mismatches in-memory, you save the overhead of serializing, dispatching, picking up, and processing jobs that are destined to fail or do nothing. The use of
match
makes the routing exceptionally clean, and the nullable return design is a smart way to avoid false negatives.
Cons/Trade-offs:
You are trading background queue worker time for a slight increase in synchronous memory and CPU usage on the current job. However, queue I/O is almost always more expensive than simple in-memory comparisons, making this a worthwhile trade.
4. Potential Issues & Bottlenecks
4. Potential Issues & Bottlenecks
While the logic is sound, there are a few architectural bottlenecks and risks introduced or maintained in this diff:
Potential N+1 Query / Lazy Loading Issues:
In the
handle
method, the activity is loaded with
Activity::with(['user.group'])
. However, inside your new
check*
methods, you are calling:
$activity->getCategory()?->getUuid()
$activity->getStage()?->getUuid()
If
category
and
stage
are Eloquent relationships and are not eager-loaded on the original
$activity
model, calling these methods inside a loop of
$totalSearches
will trigger lazy-loaded database queries.
Fix:
Update line 38 to:
$activity = Activity::with(['user.group', 'category', 'stage'])->...
Massive Memory Consumption (Pre-existing but critical):
Lines 39-41:
PHP...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.24700798,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.26030585,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.24700798,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.26030585,"top":0.09577015,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.24700798,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.26030585,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":4,"bounds":{"left":0.24700798,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":5,"bounds":{"left":0.26030585,"top":0.16121309,"width":0.17037898,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Illuminate\\Queue\\MaxAttemptsExceededException: Jiminny\\Jobs\\Activity\\DeleteTeamChurnData has been attempted too many times. — jiminny — app","depth":4,"bounds":{"left":0.24700798,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Illuminate\\Queue\\MaxAttemptsExceededException: Jiminny\\Jobs\\Activity\\DeleteTeamChurnData has been attempted too many times. — jiminny — app","depth":5,"bounds":{"left":0.26030585,"top":0.19393456,"width":0.2606383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20818 move ask jiminny reports to its own datadog metric by LakyLak · Pull Request #12056 · jiminny/app","depth":4,"bounds":{"left":0.24700798,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20818 move ask jiminny reports to its own datadog metric by LakyLak · Pull Request #12056 · jiminny/app","depth":5,"bounds":{"left":0.26030585,"top":0.22665602,"width":0.18899602,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Ask Jiminny Report Generated","depth":4,"bounds":{"left":0.24700798,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Ask Jiminny Report Generated","depth":5,"bounds":{"left":0.26030585,"top":0.25937748,"width":0.07164229,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"bounds":{"left":0.24700798,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"bounds":{"left":0.26030585,"top":0.29209897,"width":0.19331782,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.24700798,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.26030585,"top":0.32482043,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Search the CRM - HubSpot docs","depth":4,"bounds":{"left":0.24700798,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search the CRM - HubSpot docs","depth":5,"bounds":{"left":0.26030585,"top":0.3575419,"width":0.05651596,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.24700798,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.26030585,"top":0.39026338,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.24700798,"top":0.41181165,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.26030585,"top":0.42298484,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.24700798,"top":0.4445331,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.26030585,"top":0.4557063,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dashboards | Datadog","depth":4,"bounds":{"left":0.24700798,"top":0.4772546,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboards | Datadog","depth":5,"bounds":{"left":0.26030585,"top":0.4884278,"width":0.03856383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"AI Features | Datadog","depth":4,"bounds":{"left":0.24700798,"top":0.509976,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"AI Features | Datadog","depth":5,"bounds":{"left":0.26030585,"top":0.5211492,"width":0.037400264,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20493 smart instant nudge pre filtering by nikolaybiaivanov · Pull Request #12053 · jiminny/app","depth":4,"bounds":{"left":0.24700798,"top":0.54269755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jy 20493 smart instant nudge pre filtering by nikolaybiaivanov · Pull Request #12053 · jiminny/app","depth":5,"bounds":{"left":0.26030585,"top":0.55387074,"width":0.17037898,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.31432846,"top":0.54988027,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.24983378,"top":0.57701516,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.24983378,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.26080453,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.27194148,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.28307846,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.2942154,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"bounds":{"left":0.43168217,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"bounds":{"left":0.44365028,"top":0.055067837,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.4409907,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"bounds":{"left":0.33061835,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Chat","depth":12,"bounds":{"left":0.41306517,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"bounds":{"left":0.42636302,"top":0.103751,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"bounds":{"left":0.32629654,"top":0.14764565,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"bounds":{"left":0.32629654,"top":0.15003991,"width":0.1200133,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20493 smart instant nudge pre filtering by niko</tabTitle>” with “<selection>@@ -4,16 +4,17 @@445namespace Jiminny\\Component\\Nudge\\Job;5namespace Jiminny\\Component\\Nudge\\Job;667+use Carbon\\Carbon;7use Illuminate\\Bus\\Queueable;8use Illuminate\\Bus\\Queueable;8use Illuminate\\Contracts\\Queue\\ShouldQueue;9use Illuminate\\Contracts\\Queue\\ShouldQueue;9-use Illuminate\\Database\\Eloquent\\Builder;10use Illuminate\\Foundation\\Bus\\Dispatchable;10use Illuminate\\Foundation\\Bus\\Dispatchable;11use Illuminate\\Queue\\InteractsWithQueue;11use Illuminate\\Queue\\InteractsWithQueue;12use Illuminate\\Queue\\SerializesModels;12use Illuminate\\Queue\\SerializesModels;13use Illuminate\\Support\\Facades\\Log;13use Illuminate\\Support\\Facades\\Log;14use Jiminny\\Component\\Queue\\Constants;14use Jiminny\\Component\\Queue\\Constants;15use Jiminny\\Models\\Activity;15use Jiminny\\Models\\Activity;16use Jiminny\\Models\\Activity\\Search;16use Jiminny\\Models\\Activity\\Search;17+use Jiminny\\Models\\Activity\\SearchFilter;17use Jiminny\\Models\\Nudge;18use Jiminny\\Models\\Nudge;18use Jiminny\\Models\\Team;19use Jiminny\\Models\\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20493 smart instant nudge pre filtering by niko</tabTitle>” with “<selection>@@ -4,16 +4,17 @@445namespace Jiminny\\Component\\Nudge\\Job;5namespace Jiminny\\Component\\Nudge\\Job;667+use Carbon\\Carbon;7use Illuminate\\Bus\\Queueable;8use Illuminate\\Bus\\Queueable;8use Illuminate\\Contracts\\Queue\\ShouldQueue;9use Illuminate\\Contracts\\Queue\\ShouldQueue;9-use Illuminate\\Database\\Eloquent\\Builder;10use Illuminate\\Foundation\\Bus\\Dispatchable;10use Illuminate\\Foundation\\Bus\\Dispatchable;11use Illuminate\\Queue\\InteractsWithQueue;11use Illuminate\\Queue\\InteractsWithQueue;12use Illuminate\\Queue\\SerializesModels;12use Illuminate\\Queue\\SerializesModels;13use Illuminate\\Support\\Facades\\Log;13use Illuminate\\Support\\Facades\\Log;14use Jiminny\\Component\\Queue\\Constants;14use Jiminny\\Component\\Queue\\Constants;15use Jiminny\\Models\\Activity;15use Jiminny\\Models\\Activity;16use Jiminny\\Models\\Activity\\Search;16use Jiminny\\Models\\Activity\\Search;17+use Jiminny\\Models\\Activity\\SearchFilter;17use Jiminny\\Models\\Nudge;18use Jiminny\\Models\\Nudge;18use Jiminny\\Models\\Team;19use Jiminny\\Models\\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This code selection introduces a","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"pre-filtering mechanism","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Component\\Nudge\\Job","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"namespace. The primary goal is to optimize performance by validating an activity against search criteria in-memory before dispatching costly background jobs.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Core Functionality Changes","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Core Functionality Changes","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Selective Loading:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handle()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method now eager-loads","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user.group","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for activities and specifically filters for","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FREQ_INSTANTLY","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"nudges and their associated search filters.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pre-Dispatch Validation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Instead of dispatching jobs for every instant nudge, the system now calls","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activityMightMatchSearch()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Short-Circuiting:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If an activity fails a pre-filter check (e.g., mismatched user ID or duration), the job dispatch is skipped, and the event is logged with the specific mismatch reason.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Performance Metrics:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adds summary logging at the end of the process to track:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Total searches processed.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Number of skipped dispatches.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The \"Skip Rate\" percentage.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Implemented Filter Checks","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implemented Filter Checks","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The logic includes a","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"match","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expression to validate various activity attributes against","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SearchFilter","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"values:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Identity & Metadata:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Matches for","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"group_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"channel_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Temporal & Duration:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"* Validates","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"min_duration","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"max_duration","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Checks","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"start_date","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"end_date","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"using Carbon parsing.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity State:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"*","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logging:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Determines if an activity is logged to a CRM or is internal via","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"checkNotLogged","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recording:","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Uses","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"checkOnlyRecorded","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to filter based on whether a recording exists.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Categorization:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Checks for","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"category_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"stage_id","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"languages","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Note:","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The language check specifically extracts base codes (e.g.,","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"en","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"en_GB","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") to align with Elasticsearch behavior.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Logic Design","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logic Design","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nullable Returns:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filter methods return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if a match is possible (or cannot be determined), and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"only when a definitive mismatch is found.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Safe Fallback:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If a filter type is unknown (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"default","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), the system assumes a match and proceeds to dispatch, ensuring no false negatives.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":20,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a review of the changes provided in the diff, broken down by your requested structure.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Review of Changes","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Review of Changes","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This PR refactors the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ProcessNudgeSearchJob","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dispatching mechanism within the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handle()","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Query Optimization:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Eager loads necessary relationships (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"user.group","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"filters","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and scopes the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"nudges","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"relationship at the database level to only fetch","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FREQ_INSTANTLY","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"records.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In-Memory Filtering:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduces a pre-filtering mechanism (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"activityMightMatchSearch","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") that checks the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"against","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SearchFilter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"criteria before deciding to dispatch a job.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Helper Methods:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adds 13 private","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check*","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods to handle the specific comparison logic for different filter types (e.g., duration, dates, categories, stages).","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Observability:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adds structured logging to record exactly why a nudge was skipped and outputs a summary report (total, skipped, dispatched, skip rate) at the end of the execution.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Explanation of Logic","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Explanation of Logic","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The core philosophy of this change is","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"veto-based short-circuiting\"","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Previously, the job fetched all searches with instant nudges for a team's users and blindly dispatched a queue job for every single one. The heavy lifting of deciding if the activity actually matched the search was likely deferred to the queue worker and Elasticsearch.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Now, the code evaluates the filters in the current process:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"It iterates through each","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and its associated","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SearchFilter","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"s.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"It routes the filter to a specific","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check*","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method using a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"match","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"expression based on the filter's property name.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Veto:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check*","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods are designed to return","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"only if there is an absolute mismatch","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(e.g., the filter requires a minimum duration of 10, but the activity is 5).","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Pass:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the filter matches, or if the activity lacks the data needed to make a firm decision (e.g., no stage is assigned, or the filter type is unrecognized), the method returns","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". This acts as a pass, delegating the final strict evaluation to Elasticsearch later in the pipeline.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If any filter returns","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"false","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", the search is skipped, saving a queue dispatch.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. Evaluation","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. Evaluation","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Overall, this is a","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"highly effective performance optimization","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pros:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"It significantly reduces queue bloat. By filtering out obvious mismatches in-memory, you save the overhead of serializing, dispatching, picking up, and processing jobs that are destined to fail or do nothing. The use of","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"match","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"makes the routing exceptionally clean, and the nullable return design is a smart way to avoid false negatives.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cons/Trade-offs:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You are trading background queue worker time for a slight increase in synchronous memory and CPU usage on the current job. However, queue I/O is almost always more expensive than simple in-memory comparisons, making this a worthwhile trade.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"4. Potential Issues & Bottlenecks","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. Potential Issues & Bottlenecks","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"While the logic is sound, there are a few architectural bottlenecks and risks introduced or maintained in this diff:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Potential N+1 Query / Lazy Loading Issues:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handle","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method, the activity is loaded with","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Activity::with(['user.group'])","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". However, inside your new","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"check*","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"methods, you are calling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$activity->getCategory()?->getUuid()","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$activity->getStage()?->getUuid()","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"category","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"stage","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are Eloquent relationships and are not eager-loaded on the original","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$activity","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"model, calling these methods inside a loop of","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$totalSearches","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will trigger lazy-loaded database queries.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fix:","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Update line 38 to:","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"$activity = Activity::with(['user.group', 'category', 'stage'])->...","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Massive Memory Consumption (Pre-existing but critical):","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lines 39-41:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PHP","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8951985746456604555
|
6131739609835903235
|
click
|
accessibility
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -4,16 +4,17 @@445namespace Jiminny\Component\Nudge\Job;5namespace Jiminny\Component\Nudge\Job;667+use Carbon\Carbon;7use Illuminate\Bus\Queueable;8use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Contracts\Queue\ShouldQueue;9-use Illuminate\Database\Eloquent\Builder;10use Illuminate\Foundation\Bus\Dispatchable;10use Illuminate\Foundation\Bus\Dispatchable;11use Illuminate\Queue\InteractsWithQueue;11use Illuminate\Queue\InteractsWithQueue;12use Illuminate\Queue\SerializesModels;12use Illuminate\Queue\SerializesModels;13use Illuminate\Support\Facades\Log;13use Illuminate\Support\Facades\Log;14use Jiminny\Component\Queue\Constants;14use Jiminny\Component\Queue\Constants;15use Jiminny\Models\Activity;15use Jiminny\Models\Activity;16use Jiminny\Models\Activity\Search;16use Jiminny\Models\Activity\Search;17+use Jiminny\Models\Activity\SearchFilter;17use Jiminny\Models\Nudge;18use Jiminny\Models\Nudge;18use Jiminny\Models\Team;19use Jiminny\Models\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20493 smart instant nudge pre filtering by niko</tabTitle>” with “<selection>@@ -4,16 +4,17 @@445namespace Jiminny\Component\Nudge\Job;5namespace Jiminny\Component\Nudge\Job;667+use Carbon\Carbon;7use Illuminate\Bus\Queueable;8use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Contracts\Queue\ShouldQueue;9-use Illuminate\Database\Eloquent\Builder;10use Illuminate\Foundation\Bus\Dispatchable;10use Illuminate\Foundation\Bus\Dispatchable;11use Illuminate\Queue\InteractsWithQueue;11use Illuminate\Queue\InteractsWithQueue;12use Illuminate\Queue\SerializesModels;12use Illuminate\Queue\SerializesModels;13use Illuminate\Support\Facades\Log;13use Illuminate\Support\Facades\Log;14use Jiminny\Component\Queue\Constants;14use Jiminny\Component\Queue\Constants;15use Jiminny\Models\Activity;15use Jiminny\Models\Activity;16use Jiminny\Models\Activity\Search;16use Jiminny\Models\Activity\Search;17+use Jiminny\Models\Activity\SearchFilter;17use Jiminny\Models\Nudge;18use Jiminny\Models\Nudge;18use Jiminny\Models\Team;19use Jiminny\Models\Team;1920@@ -34,37 +35,201 @@343535public function handle(): void36public function handle(): void36 {37 {37-/** @var Activity $activity */38+$activity = Activity::with(['user.group'])->uuid($this->activityUuid, false)->firstOrFail();38-$activity = Activity::with(['user'])->uuid($this->activityUuid, false)->firstOrFail();39-40-/** @var Team $team */41$team = Team::with(['users'])->findOrFail($activity->user->team_id);39$team = Team::with(['users'])->findOrFail($activity->user->team_id);424043- Log::info(__METHOD__ . " Running for team $team->id.", [44-'activity_id' => $activity->id,45-'activity_uuid' => $this->activityUuid,46-'team_id' => $team->id,47- ]);48-49$usersIds = $team->users->pluck('id')->toArray();41$usersIds = $team->users->pluck('id')->toArray();504251-$searchesWithImmediateNudges = Activity\Search::with(['nudges'])43+$searchesWithImmediateNudges = Activity\Search::with([44+'nudges' => fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY),45+'filters',46+ ])52 ->whereIn('user_id', $usersIds)47 ->whereIn('user_id', $usersIds)53- ->whereHas('nudges', function (Builder $query) {48+ ->whereHas('nudges', fn ($q) => $q->where('frequency', Nudge::FREQ_INSTANTLY))54-$query->where('frequency', Nudge::FREQ_INSTANTLY);49+ ->get();55- })->get();50+51+$totalSearches = $searchesWithImmediateNudges->count();52+$skippedCount = 0;53+54+$searchesWithImmediateNudges->each(function (Search $search) use ($activity, &$skippedCount): void {55+$matchResult = $this->activityMightMatchSearch($activity, $search);565657-$searchesWithImmediateNudges->each(function (Search $search): void {57+if (! $matchResult['matches']) {58-$search->nudges->each(function (Nudge $nudge): void {58+$skippedCount++;59-// Filter only nudges that are set to be sent instantly59+ Log::info('Pre-filter skipped nudge dispatch', [60-if ($nudge->frequency === Nudge::FREQ_INSTANTLY) {60+'activity_uuid' => $activity->getUuid(),61- Log::info("Dispatching ProcessNudgeSearchJob for nudge $nudge->id.");61+'search_id' => $search->getId(),62+'mismatched_filter' => $matchResult['mismatch_reason'],63+'filter_value' => $matchResult['filter_value'],64+ ]);626563- ProcessNudgeSearchJob::dispatch($nudge);66+return;67+ }646865- Log::info("ProcessNudgeSearchJob dispatched for nudge $nudge->id.");69+$search->nudges->each(fn ($nudge) => ProcessNudgeSearchJob::dispatch($nudge));66- }67- });68 });70 });71+72+if ($totalSearches > 0) {73+ Log::info('Nudge pre-filter results', [74+'activity_uuid' => $activity->getUuid(),75+'total_searches' => $totalSearches,76+'skipped' => $skippedCount,77+'dispatched' => $totalSearches - $skippedCount,78+'skip_rate' => round(($skippedCount / $totalSearches) * 100, 2) . '%',79+ ]);80+ }81+ }82+83+private function activityMightMatchSearch(Activity $activity, Search $search): array84+ {85+foreach ($search->filters as $filter) {86+$filterName = $filter->getFilterProperty();87+88+$result = match ($filterName) {89+'group_id' => $this->checkGroupId($activity, $filter),90+'user_id' => $this->checkUserId($activity, $filter),91+'channel_id' => $this->checkChannelId($activity, $filter),92+'provider_id' => $this->checkProviderId($activity, $filter),93+'category_id' => $this->checkCategoryId($activity, $filter),94+'min_duration' => $this->checkMinDuration($activity, $filter),95+'max_duration' => $this->checkMaxDuration($activity, $filter),96+'not_logged' => $this->checkNotLogged($activity, $filter),97+'only_recorded' => $this->checkOnlyRecorded($activity, $filter),98+'languages' => $this->checkLanguages($activity, $filter),99+'stage_id' => $this->checkStageId($activity, $filter),100+'start_date' => $this->checkStartDate($activity, $filter),101+'end_date' => $this->checkEndDate($activity, $filter),102+default => null, // Unknown filter - can't pre-check, assume might match103+ };104+105+if ($result === false) {106+return [107+'matches' => false,108+'mismatch_reason' => $filterName,109+'filter_value' => $filter->getFilterValue(),110+ ];111+ }112+ }113+114+return ['matches' => true];115+ }116+117+private function checkGroupId(Activity $activity, SearchFilter $filter): ?bool118+ {119+$groupUuid = $activity->user->getGroup()?->getUuid();120+if ($groupUuid === null) {121+return null;122+ }123+124+return $filter->getFilterValue() === $groupUuid ? null : false;125+ }126+127+private function checkUserId(Activity $activity, SearchFilter $filter): ?bool128+ {129+return $filter->getFilterValue() === $activity->user->getUuid() ? null : false;130+ }131+132+private function checkChannelId(Activity $activity, SearchFilter $filter): ?bool133+ {134+return $filter->getFilterValue() === $activity->getType() ? null : false;135+ }136+137+private function checkProviderId(Activity $activity, SearchFilter $filter): ?bool138+ {139+return $filter->getFilterValue() === $activity->getProvider() ? null : false;140+ }141+142+private function checkCategoryId(Activity $activity, SearchFilter $filter): ?bool143+ {144+$categoryId = $activity->getCategory()?->getUuid();145+if ($categoryId === null) {146+return null;147+ }148+149+return $filter->getFilterValue() === $categoryId ? null : false;150+ }151+152+private function checkMinDuration(Activity $activity, SearchFilter $filter): ?bool153+ {154+return $activity->getDuration() >= (float) $filter->getFilterValue() ? null : false;155+ }156+157+private function checkMaxDuration(Activity $activity, SearchFilter $filter): ?bool158+ {159+return $activity->getDuration() <= (float) $filter->getFilterValue() ? null : false;160+ }161+162+private function checkNotLogged(Activity $activity, SearchFilter $filter): ?bool163+ {164+if (! $filter->getFilterValue()) {165+return null;166+ }167+168+$isLogged = $activity->getCrmProviderId() !== null;169+$isInternal = $activity->isInternal();170+171+return (! $isLogged || $isInternal) ? null : false;172+ }173+174+private function checkOnlyRecorded(Activity $activity, SearchFilter $filter): ?bool175+ {176+$filterValue = (int) $filter->getFilterValue();177+$hasRecording = $activity->recording_state !== Activity::RECORDING_OFF;178+179+return match ($filterValue) {180+1 => $hasRecording ? null : false, // Only recorded181+2 => ! $hasRecording ? null : false, // Only non-recorded182+default => null,183+ };184+ }185+186+private function checkStageId(Activity $activity, SearchFilter $filter): ?bool187+ {188+$stageId = $activity->getStage()?->getUuid();189+if ($stageId === null) {190+return null; // No stage, let ES handle191+ }192+193+return $filter->getFilterValue() === $stageId ? null : false;194+ }195+196+private function checkStartDate(Activity $activity, SearchFilter $filter): ?bool197+ {198+$startTime = $activity->getActualStartTime();199+if ($startTime === null) {200+return null;201+ }202+203+$filterDate = Carbon::parse($filter->getFilterValue());204+205+return $startTime->gte($filterDate) ? null : false;206+ }207+208+private function checkEndDate(Activity $activity, SearchFilter $filter): ?bool209+ {210+$endTime = $activity->getActualEndTime();211+if ($endTime === null) {212+return null;213+ }214+215+$filterDate = Carbon::parse($filter->getFilterValue())->endOfDay();216+217+return $endTime->lte($filterDate) ? null : false;218+ }219+220+private function checkLanguages(Activity $activity, SearchFilter $filter): ?bool221+ {222+$activityLanguage = $activity->getLanguageCode();223+if ($activityLanguage === null) {224+return null;225+ }226+227+// Filter values are always base language codes (e.g., 'en', 'fr').228+// Extract base code from activity locale (e.g., en_GB -> en) to match ES query behavior.229+// See LanguageFilterDefinition::getQueries() which uses regex: $language . '_[a-z]{2}'230+$activityLangCode = explode('_', $activityLanguage)[0];231+$filterLanguage = $filter->getFilterValue();232+233+return $activityLangCode === $filterLanguage ? null : false;69 }234 }70}235}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
This code selection introduces a
pre-filtering mechanism
to the
Jiminny\Component\Nudge\Job
namespace. The primary goal is to optimize performance by validating an activity against search criteria in-memory before dispatching costly background jobs.
Core Functionality Changes
Core Functionality Changes
Selective Loading:
The
handle()
method now eager-loads
user.group
for activities and specifically filters for
FREQ_INSTANTLY
nudges and their associated search filters.
Pre-Dispatch Validation:
Instead of dispatching jobs for every instant nudge, the system now calls
activityMightMatchSearch()
.
Short-Circuiting:
If an activity fails a pre-filter check (e.g., mismatched user ID or duration), the job dispatch is skipped, and the event is logged with the specific mismatch reason.
Performance Metrics:
Adds summary logging at the end of the process to track:
Total searches processed.
Number of skipped dispatches.
The "Skip Rate" percentage.
Implemented Filter Checks
Implemented Filter Checks
The logic includes a
match
expression to validate various activity attributes against
SearchFilter
values:
Identity & Metadata:
Matches for
group_id
,
user_id
,
channel_id
, and
provider_id
.
Temporal & Duration:
* Validates
min_duration
and
max_duration
.
Checks
start_date
and
end_date
using Carbon parsing.
Activity State:
*
Logging:
Determines if an activity is logged to a CRM or is internal via
checkNotLogged
.
Recording:
Uses
checkOnlyRecorded
to filter based on whether a recording exists.
Categorization:
Checks for
category_id
,
stage_id
, and
languages
.
Note:
The language check specifically extracts base codes (e.g.,
en
from
en_GB
) to align with Elasticsearch behavior.
Logic Design
Logic Design
Nullable Returns:
Filter methods return
null
if a match is possible (or cannot be determined), and
false
only when a definitive mismatch is found.
Safe Fallback:
If a filter type is unknown (
default
), the system assumes a match and proceeds to dispatch, ensuring no false negatives.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt
You said what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks
You said
what is the logic behind diff. Act as code reviewer. First review the changes then explain it and then evaluate. Try to find potential issues and bottlenecks
Expand
Listen
Show more options
Show thinking
Gemini said
Gemini said
Here is a review of the changes provided in the diff, broken down by your requested structure.
1. Review of Changes
1. Review of Changes
This PR refactors the
ProcessNudgeSearchJob
dispatching mechanism within the
handle()
method.
Query Optimization:
Eager loads necessary relationships (
user.group
,
filters
) and scopes the
nudges
relationship at the database level to only fetch
FREQ_INSTANTLY
records.
In-Memory Filtering:
Introduces a pre-filtering mechanism (
activityMightMatchSearch
) that checks the
Activity
against
SearchFilter
criteria before deciding to dispatch a job.
Helper Methods:
Adds 13 private
check*
methods to handle the specific comparison logic for different filter types (e.g., duration, dates, categories, stages).
Observability:
Adds structured logging to record exactly why a nudge was skipped and outputs a summary report (total, skipped, dispatched, skip rate) at the end of the execution.
2. Explanation of Logic
2. Explanation of Logic
The core philosophy of this change is
"veto-based short-circuiting"
.
Previously, the job fetched all searches with instant nudges for a team's users and blindly dispatched a queue job for every single one. The heavy lifting of deciding if the activity actually matched the search was likely deferred to the queue worker and Elasticsearch.
Now, the code evaluates the filters in the current process:
It iterates through each
Search
and its associated
SearchFilter
s.
It routes the filter to a specific
check*
method using a
match
expression based on the filter's property name.
The Veto:
The
check*
methods are designed to return
false
only if there is an absolute mismatch
(e.g., the filter requires a minimum duration of 10, but the activity is 5).
The Pass:
If the filter matches, or if the activity lacks the data needed to make a firm decision (e.g., no stage is assigned, or the filter type is unrecognized), the method returns
null
. This acts as a pass, delegating the final strict evaluation to Elasticsearch later in the pipeline.
If any filter returns
false
, the search is skipped, saving a queue dispatch.
3. Evaluation
3. Evaluation
Overall, this is a
highly effective performance optimization
.
Pros:
It significantly reduces queue bloat. By filtering out obvious mismatches in-memory, you save the overhead of serializing, dispatching, picking up, and processing jobs that are destined to fail or do nothing. The use of
match
makes the routing exceptionally clean, and the nullable return design is a smart way to avoid false negatives.
Cons/Trade-offs:
You are trading background queue worker time for a slight increase in synchronous memory and CPU usage on the current job. However, queue I/O is almost always more expensive than simple in-memory comparisons, making this a worthwhile trade.
4. Potential Issues & Bottlenecks
4. Potential Issues & Bottlenecks
While the logic is sound, there are a few architectural bottlenecks and risks introduced or maintained in this diff:
Potential N+1 Query / Lazy Loading Issues:
In the
handle
method, the activity is loaded with
Activity::with(['user.group'])
. However, inside your new
check*
methods, you are calling:
$activity->getCategory()?->getUuid()
$activity->getStage()?->getUuid()
If
category
and
stage
are Eloquent relationships and are not eager-loaded on the original
$activity
model, calling these methods inside a loop of
$totalSearches
will trigger lazy-loaded database queries.
Fix:
Update line 38 to:
$activity = Activity::with(['user.group', 'category', 'stage'])->...
Massive Memory Consumption (Pre-existing but critical):
Lines 39-41:
PHP...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
7387
|
329
|
15
|
2026-05-08T08:44:54.241216+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778229894241_m1.jpg...
|
PhpStorm
|
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Find in Files
31 matches in 11 files
File mask:
*. Find in Files
31 matches in 11 files
File mask:
*.php
*.php
Auto
*.php
Filter Search Results
Pin Window
Search History
TYPE_ASK_JIMINNY
New Line
Match case
Words
Regex
Replace History
Replace
New Line
Preserve case
In Project
Module
Directory
Scope
Module
/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports
/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot
/Users/lukas/jiminny/app/app/Jobs/Crm/Delete
/Users/lukas/jiminny/app/app/Listeners/Crm
/Users/lukas/jiminny/app/app/Jobs/Crm
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/ServiceTraits
/Users/lukas/jiminny/app/app/Exceptions
/Users/lukas/jiminny/app/app/Component/Queue/Job
/Users/lukas/jiminny/app/app/Listeners/AutomatedReports/UserPilot
/Users/lukas/jiminny/app/app/Events/Crm
/Users/lukas/jiminny/app/app/Jobs/AutomatedReports
/Users/lukas/jiminny/app/app/Listeners/Activities/Coaching/UserPilot
/Users/lukas/jiminny/app/app/Listeners/Activities/ActivityProvider/UserPilot
/Users/lukas/jiminny/app/app/Jobs/Activity/PushSummaryToCrm
/Users/lukas/jiminny/app/app/Repositories/Crm
/Users/lukas/jiminny/app/app/Http/Controllers/API/UserAutomatedReports
/Users/lukas/jiminny/app/app/Services/Crm/Salesforce
/Users/lukas/jiminny/app/app/Providers
/Users/lukas/jiminny/app/app/Services/Crm/IntegrationApp
/Users/lukas/jiminny/app/app/Events/Activities/Crm
/Users/lukas/jiminny/app/app/Listeners/Playbooks
/Users/lukas/jiminny/app/app/Console/Commands/Crm
/Users/lukas/jiminny/app/app/Services/Crm
/Users/lukas/jiminny/app/app/Http/Controllers
/Users/lukas/jiminny/app/app/Console/Commands/Reports
/Users/lukas/jiminny/app/app/VO/Repository/OnDemandActivitySearch
/Users/lukas/jiminny/app/app/Listeners/Activities/Conferences/UserPilot
/Users/lukas/jiminny/app/app/Http/Controllers/Webhook
/Users/lukas/jiminny/app/resources/views/emails/reports
/Users/lukas/jiminny/app/app/Mail/Reports
/Users/lukas/jiminny/app/app/Repositories
/Users/lukas/jiminny/app/app/Component/ActivitySearch/Service
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Salesforce
/Users/lukas/jiminny/app/routes
/Users/lukas/jiminny/app/app/Console/Commands
/Users/lukas/jiminny/app/database/migrations
/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/325d461a-c90f-430a-99d4-6ddfce0c61d7
/Users/lukas/jiminny/app/app/Http/Controllers/API/V2
/Users/lukas/jiminny/app/app/Jobs/Crm/Hubspot
/Users/lukas/jiminny/app/app/Component/DealInsights
/Users/lukas/jiminny/app/app/Policies
/Users/lukas/jiminny/app/app/Services/Crm/Helpers
/Users/lukas/jiminny/app/app/Models
/Users/lukas/jiminny/app/app/Listeners/Teams
/Users/lukas/jiminny/app/app/Jobs/Crm/Salesforce
/Users/lukas/jiminny/app/app
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Journal
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/OpportunitySyncStrategy
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Webhook
/Users/lukas/jiminny/app/storage/logs
/Users/lukas/jiminny/app
/Users/lukas/jiminny/app/app/Services/Internal
/Users/lukas/jiminny/app/app/Listeners/Transcription
/Users/lukas/jiminny/app/tests/Unit/Listeners/Teams
/Users/lukas/jiminny/app/app/Models/Crm
/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/91133dfa-8d71-4e12-bfb8-fec7f1afba8f
/Users/lukas/jiminny/app/app/Observers
/Users/lukas/jiminny/app/app/Services/Mail
/Users/lukas/jiminny/app/app/Console/Commands/Activities
/Users/lukas/jiminny/app/app/Console/Commands/Activities/Migrator
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/ServiceTraits
/Users/lukas/jiminny/app/app/Jobs/User
/Users/lukas/jiminny/app/app/Models/Activity
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/Webhook
/Users/lukas/jiminny/app/app/Component/AiAutomation/Listeners/PendingAnalysis
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot
/Users/lukas/jiminny/app/app/Component/ActivitySearch/FilterDefinition/DealInsights
/Users/lukas/jiminny/app/app/Services/Crm/DecorateActivity
/Users/lukas/jiminny/app/app/Component/Activity/Event...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Find in Files","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"31 matches in 11 files","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"File mask:","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"*.php","depth":1,"on_screen":true,"value":"*.php","role_description":"combo box","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"*.php","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Auto","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXTextField","text":"*.php","depth":2,"on_screen":true,"value":"*.php","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pin Window","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"TYPE_ASK_JIMINNY","depth":2,"on_screen":true,"value":"TYPE_ASK_JIMINNY","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match case","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":1,"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":1,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":2,"on_screen":false,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":1,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":1,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.024444444},"on_screen":false,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In Project","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Module","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Directory","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Scope","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Module","depth":1,"bounds":{"left":0.0,"top":0.0,"width":0.20833333,"height":0.037777778},"on_screen":false,"role_description":"pop up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports","depth":1,"bounds":{"left":0.0,"top":0.0,"width":0.4125,"height":0.037777778},"on_screen":false,"value":"/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports","role_description":"combo box","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Hubspot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Crm/Delete","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/ServiceTraits","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Exceptions","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/Queue/Job","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/AutomatedReports/UserPilot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Events/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/AutomatedReports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Activities/Coaching/UserPilot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Activities/ActivityProvider/UserPilot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Activity/PushSummaryToCrm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Repositories/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Http/Controllers/API/UserAutomatedReports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Salesforce","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Providers","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/IntegrationApp","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Events/Activities/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Playbooks","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Console/Commands/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Http/Controllers","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Console/Commands/Reports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/VO/Repository/OnDemandActivitySearch","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Activities/Conferences/UserPilot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Http/Controllers/Webhook","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/resources/views/emails/reports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Mail/Reports","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Repositories","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/ActivitySearch/Service","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Salesforce","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/routes","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Console/Commands","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/database/migrations","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/325d461a-c90f-430a-99d4-6ddfce0c61d7","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Http/Controllers/API/V2","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Crm/Hubspot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/DealInsights","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Policies","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Helpers","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Models","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Teams","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/Crm/Salesforce","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Journal","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/OpportunitySyncStrategy","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Webhook","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/storage/logs","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Internal","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Listeners/Transcription","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Listeners/Teams","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Models/Crm","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/91133dfa-8d71-4e12-bfb8-fec7f1afba8f","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Observers","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Mail","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Console/Commands/Activities","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Console/Commands/Activities/Migrator","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/ServiceTraits","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Jobs/User","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Models/Activity","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/Webhook","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/AiAutomation/Listeners/PendingAnalysis","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/ActivitySearch/FilterDefinition/DealInsights","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Services/Crm/DecorateActivity","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/Users/lukas/jiminny/app/app/Component/Activity/Event","depth":6,"on_screen":false,"role_description":"text"}]...
|
8951491371809239685
|
-338939406892570677
|
click
|
accessibility
|
NULL
|
Find in Files
31 matches in 11 files
File mask:
*. Find in Files
31 matches in 11 files
File mask:
*.php
*.php
Auto
*.php
Filter Search Results
Pin Window
Search History
TYPE_ASK_JIMINNY
New Line
Match case
Words
Regex
Replace History
Replace
New Line
Preserve case
In Project
Module
Directory
Scope
Module
/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports
/Users/lukas/jiminny/app/app/Services/Kiosk/AutomatedReports
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot
/Users/lukas/jiminny/app/app/Jobs/Crm/Delete
/Users/lukas/jiminny/app/app/Listeners/Crm
/Users/lukas/jiminny/app/app/Jobs/Crm
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/ServiceTraits
/Users/lukas/jiminny/app/app/Exceptions
/Users/lukas/jiminny/app/app/Component/Queue/Job
/Users/lukas/jiminny/app/app/Listeners/AutomatedReports/UserPilot
/Users/lukas/jiminny/app/app/Events/Crm
/Users/lukas/jiminny/app/app/Jobs/AutomatedReports
/Users/lukas/jiminny/app/app/Listeners/Activities/Coaching/UserPilot
/Users/lukas/jiminny/app/app/Listeners/Activities/ActivityProvider/UserPilot
/Users/lukas/jiminny/app/app/Jobs/Activity/PushSummaryToCrm
/Users/lukas/jiminny/app/app/Repositories/Crm
/Users/lukas/jiminny/app/app/Http/Controllers/API/UserAutomatedReports
/Users/lukas/jiminny/app/app/Services/Crm/Salesforce
/Users/lukas/jiminny/app/app/Providers
/Users/lukas/jiminny/app/app/Services/Crm/IntegrationApp
/Users/lukas/jiminny/app/app/Events/Activities/Crm
/Users/lukas/jiminny/app/app/Listeners/Playbooks
/Users/lukas/jiminny/app/app/Console/Commands/Crm
/Users/lukas/jiminny/app/app/Services/Crm
/Users/lukas/jiminny/app/app/Http/Controllers
/Users/lukas/jiminny/app/app/Console/Commands/Reports
/Users/lukas/jiminny/app/app/VO/Repository/OnDemandActivitySearch
/Users/lukas/jiminny/app/app/Listeners/Activities/Conferences/UserPilot
/Users/lukas/jiminny/app/app/Http/Controllers/Webhook
/Users/lukas/jiminny/app/resources/views/emails/reports
/Users/lukas/jiminny/app/app/Mail/Reports
/Users/lukas/jiminny/app/app/Repositories
/Users/lukas/jiminny/app/app/Component/ActivitySearch/Service
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Salesforce
/Users/lukas/jiminny/app/routes
/Users/lukas/jiminny/app/app/Console/Commands
/Users/lukas/jiminny/app/database/migrations
/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/325d461a-c90f-430a-99d4-6ddfce0c61d7
/Users/lukas/jiminny/app/app/Http/Controllers/API/V2
/Users/lukas/jiminny/app/app/Jobs/Crm/Hubspot
/Users/lukas/jiminny/app/app/Component/DealInsights
/Users/lukas/jiminny/app/app/Policies
/Users/lukas/jiminny/app/app/Services/Crm/Helpers
/Users/lukas/jiminny/app/app/Models
/Users/lukas/jiminny/app/app/Listeners/Teams
/Users/lukas/jiminny/app/app/Jobs/Crm/Salesforce
/Users/lukas/jiminny/app/app
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Journal
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/OpportunitySyncStrategy
/Users/lukas/jiminny/app/app/Services/Crm/Hubspot/Webhook
/Users/lukas/jiminny/app/storage/logs
/Users/lukas/jiminny/app
/Users/lukas/jiminny/app/app/Services/Internal
/Users/lukas/jiminny/app/app/Listeners/Transcription
/Users/lukas/jiminny/app/tests/Unit/Listeners/Teams
/Users/lukas/jiminny/app/app/Models/Crm
/Users/lukas/Library/Application Support/JetBrains/PhpStorm2026.1/consoles/db/91133dfa-8d71-4e12-bfb8-fec7f1afba8f
/Users/lukas/jiminny/app/app/Observers
/Users/lukas/jiminny/app/app/Services/Mail
/Users/lukas/jiminny/app/app/Console/Commands/Activities
/Users/lukas/jiminny/app/app/Console/Commands/Activities/Migrator
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/ServiceTraits
/Users/lukas/jiminny/app/app/Jobs/User
/Users/lukas/jiminny/app/app/Models/Activity
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot/Webhook
/Users/lukas/jiminny/app/app/Component/AiAutomation/Listeners/PendingAnalysis
/Users/lukas/jiminny/app/tests/Unit/Services/Crm/Hubspot
/Users/lukas/jiminny/app/app/Component/ActivitySearch/FilterDefinition/DealInsights
/Users/lukas/jiminny/app/app/Services/Crm/DecorateActivity
/Users/lukas/jiminny/app/app/Component/Activity/Event...
|
7385
|
NULL
|
NULL
|
NULL
|
|
16677
|
747
|
2
|
2026-05-11T09:15:56.048129+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778490956048_m2.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEvent.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use Illuminate\Support\Facades\Log;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
$users = $this->resolveUsers($automatedReport);
if (empty($users)) {
Log::warning('[UserPilot] No recipients found for automated report', [
'report_id' => $automatedReport->getId(),
'is_ask_jiminny' => $automatedReport->isAskJiminnyReport(),
]);
return;
}
Log::info('[UserPilot] Sending automated report event', [
'report_id' => $automatedReport->getId(),
'event_name' => $eventName,
'recipient_count' => count($users),
]);
try {
foreach ($users as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
Log::error('[UserPilot] Failed to send automated report event', [
'report_id' => $automatedReport->getId(),
'error' => $e->getMessage(),
]);
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09541223,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.36901596,"top":0.19952115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.37832448,"top":0.19952115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.38763297,"top":0.19952115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.19792499,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4039229,"top":0.19792499,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n $users = $this->resolveUsers($automatedReport);\n\n if (empty($users)) {\n Log::warning('[UserPilot] No recipients found for automated report', [\n 'report_id' => $automatedReport->getId(),\n 'is_ask_jiminny' => $automatedReport->isAskJiminnyReport(),\n ]);\n\n return;\n }\n\n Log::info('[UserPilot] Sending automated report event', [\n 'report_id' => $automatedReport->getId(),\n 'event_name' => $eventName,\n 'recipient_count' => count($users),\n ]);\n\n try {\n foreach ($users as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n Log::error('[UserPilot] Failed to send automated report event', [\n 'report_id' => $automatedReport->getId(),\n 'error' => $e->getMessage(),\n ]);\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n $users = $this->resolveUsers($automatedReport);\n\n if (empty($users)) {\n Log::warning('[UserPilot] No recipients found for automated report', [\n 'report_id' => $automatedReport->getId(),\n 'is_ask_jiminny' => $automatedReport->isAskJiminnyReport(),\n ]);\n\n return;\n }\n\n Log::info('[UserPilot] Sending automated report event', [\n 'report_id' => $automatedReport->getId(),\n 'event_name' => $eventName,\n 'recipient_count' => count($users),\n ]);\n\n try {\n foreach ($users as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n Log::error('[UserPilot] Failed to send automated report event', [\n 'report_id' => $automatedReport->getId(),\n 'error' => $e->getMessage(),\n ]);\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.6296542,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.6409575,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.64827126,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"bounds":{"left":0.42885637,"top":0.09736632,"width":0.5711436,"height":0.8818835},"on_screen":true,"lines":[{"char_start":207,"char_count":30,"bounds":{"left":0.42885637,"top":0.0,"width":0.07513298,"height":0.014365523}},{"char_start":237,"char_count":36,"bounds":{"left":0.42885637,"top":0.0,"width":0.09075798,"height":0.014365523}},{"char_start":273,"char_count":32,"bounds":{"left":0.42885637,"top":0.0,"width":0.080119684,"height":0.014365523}},{"char_start":305,"char_count":79,"bounds":{"left":0.42885637,"top":0.0,"width":0.20212767,"height":0.014365523}},{"char_start":384,"char_count":18,"bounds":{"left":0.42885637,"top":0.0,"width":0.043882977,"height":0.014365523}},{"char_start":402,"char_count":21,"bounds":{"left":0.42885637,"top":0.0,"width":0.051861703,"height":0.014365523}},{"char_start":423,"char_count":48,"bounds":{"left":0.42885637,"top":0.008778931,"width":0.12167553,"height":0.014365523}},{"char_start":471,"char_count":72,"bounds":{"left":0.42885637,"top":0.026336791,"width":0.18384309,"height":0.014365523}},{"char_start":543,"char_count":40,"bounds":{"left":0.42885637,"top":0.043894652,"width":0.10106383,"height":0.014365523}},{"char_start":583,"char_count":41,"bounds":{"left":0.42885637,"top":0.061452515,"width":0.10372341,"height":0.014365523}},{"char_start":624,"char_count":72,"bounds":{"left":0.42885637,"top":0.079010375,"width":0.18384309,"height":0.014365523}},{"char_start":696,"char_count":219,"bounds":{"left":0.42885637,"top":0.096568234,"width":0.56515956,"height":0.014365523}},{"char_start":915,"char_count":83,"bounds":{"left":0.42885637,"top":0.11412609,"width":0.21243352,"height":0.014365523}},{"char_start":998,"char_count":20,"bounds":{"left":0.42885637,"top":0.13168396,"width":0.04920213,"height":0.014365523}},{"char_start":1018,"char_count":17,"bounds":{"left":0.42885637,"top":0.14924182,"width":0.041223403,"height":0.014365523}},{"char_start":1035,"char_count":203,"bounds":{"left":0.42885637,"top":0.16679968,"width":0.52360374,"height":0.014365523}},{"char_start":1238,"char_count":22,"bounds":{"left":0.42885637,"top":0.18435754,"width":0.05418883,"height":0.014365523}},{"char_start":1260,"char_count":23,"bounds":{"left":0.42885637,"top":0.2019154,"width":0.056848403,"height":0.014365523}},{"char_start":1283,"char_count":10,"bounds":{"left":0.42885637,"top":0.21947326,"width":0.023271276,"height":0.014365523}},{"char_start":1293,"char_count":27,"bounds":{"left":0.42885637,"top":0.23703113,"width":0.06715426,"height":0.014365523}},{"char_start":1320,"char_count":26,"bounds":{"left":0.42885637,"top":0.254589,"width":0.06482713,"height":0.014365523}},{"char_start":1346,"char_count":23,"bounds":{"left":0.42885637,"top":0.27214685,"width":0.056848403,"height":0.014365523}},{"char_start":1369,"char_count":28,"bounds":{"left":0.42885637,"top":0.2897047,"width":0.06981383,"height":0.014365523}},{"char_start":1397,"char_count":57,"bounds":{"left":0.42885637,"top":0.30726257,"width":0.14494681,"height":0.014365523}}],"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"on_screen":false,"role_description":"text"}]...
|
8950518612769115092
|
-3030890849699399101
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
use Illuminate\Support\Facades\Log;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
$users = $this->resolveUsers($automatedReport);
if (empty($users)) {
Log::warning('[UserPilot] No recipients found for automated report', [
'report_id' => $automatedReport->getId(),
'is_ask_jiminny' => $automatedReport->isAskJiminnyReport(),
]);
return;
}
Log::info('[UserPilot] Sending automated report event', [
'report_id' => $automatedReport->getId(),
'event_name' => $eventName,
'recipient_count' => count($users),
]);
try {
foreach ($users as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
Log::error('[UserPilot] Failed to send automated report event', [
'report_id' => $automatedReport->getId(),
'error' => $e->getMessage(),
]);
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity...
|
16674
|
NULL
|
NULL
|
NULL
|
|
19859
|
851
|
8
|
2026-05-11T13:41:14.505425+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778506874505_m2.jpg...
|
Firefox
|
JY-20725 add HS rate limit handling on activities JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/12066/changes
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (34)
Pull requests
(
34
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (4)
Security and quality
(
4
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.16888298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.16140293,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.18994413,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.24980047,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (34)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"34","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (4)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8948402273465551383
|
-4358051144720002422
|
idle
|
accessibility
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
app
app
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues(g then i)
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Pull requests (34)
Pull requests
(
34
)
Agents
Agents
Actions
Actions
Wiki
Wiki
Security and quality (4)
Security and quality
(
4
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
9295
|
421
|
2
|
2026-05-08T12:37:01.524286+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778243821524_m1.jpg...
|
Firefox
|
Jy 20820 es reindex stream model hydration by Vasi Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/12059/changes#diff-440 github.com/jiminny/app/pull/12059/changes#diff-440beab30ba7b8b55f8e107df64f066144059c5088901522087f42a83f2d2657...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20820 es reindex stream model hydration by Vasi</tabTitle>” with “<selection>@@ -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
This selection contains unit tests for the
LoadDocumentsAction
class, specifically focusing on the
loadDocuments
method. The tests verify how the system hydrates search engine document models from database entities, handling updates, deletions, and exceptions.
Core Functionality Tested
Core Functionality Tested
The tests use a mocked
EntityQueryBuilder
and anonymous
Searchable
classes to simulate different hydration scenarios:
Document Updates:
Verifies that active entities are correctly transformed into
Document
objects containing the expected ID and data (indexable attributes).
Document Deletions:
Ensures that entities flagged as
isDeleted
are processed for removal from the search index.
Mixed Results:
Validates the ability to process a collection containing both updateable and deletable entities in a single stream.
Empty Result Sets:
Confirms the system handles empty entity lists gracefully without errors.
Error Handling & Resilience
Error Handling & Resilience
Exception Catching:
A specific test case (
testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes
) ensures that if a single entity fails to hydrate due to an exception, the system:
Logs the error to
Sentry
using a
SyncActivityException
.
Continues processing other valid entities in the stream rather than failing the entire batch.
Technical Implementation Details
Technical Implementation Details
Mocking Strategy:
Uses
createMock
for dependencies and a custom
createEntityMock
helper to generate anonymous classes implementing the
Searchable
interface.
Assertions:
Utilizes PHPUnit assertions like
assertEquals
,
assertInstanceOf
, and
count
to verify the state of the returned result object.
Iterator Usage:
The
createQueryBuilderMock
uses an
ArrayIterator
to simulate a database cursor, reflecting the streaming nature of the hydration process.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt
You said Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection. Instructions: Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language. Highlight key changes: Break down the structural or logical changes made in the code. Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs. Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.
You said
Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection.
Instructions:
Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language.
Highlight key changes: Break down the structural or logical changes made in the code.
Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs.
Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.
Expand
Listen
Show more options
Show thinking
Gemini said
Gemini said
Core Objective
Core Objective
This selection introduces a comprehensive suite of PHPUnit tests for the
LoadDocumentsAction
class. The goal is to verify the logic of the
loadDocuments
method, ensuring it correctly transforms streamed database entities into search-indexable documents, accurately segregates them into "update" and "delete" queues, and handles individual hydration failures without crashing the entire batch process.
Key Behaviors Verified
Key Behaviors Verified
Standard Hydration:
Confirms that active entities (
isDeleted = false
) are properly parsed into
Document
objects containing the correct IDs and indexable attributes.
Deletion Handling:
Ensures entities flagged as deleted (
isDeleted = true
) are added to the deletion queue and excluded from the update queue.
Mixed Batch Processing:
Validates the system's ability to process a single stream containing both active and deleted entities, successfully splitting them into their respective queues.
Empty Result Sets:
Verifies that providing an empty list of entity IDs resolves gracefully, returning zero documents to update or delete without error.
Fault Tolerance:
Ensures that if a specific entity throws an exception during attribute generation, the error is logged to Sentry (as a
SyncActivityException
), and the system successfully continues processing the remaining valid entities.
Structural & Mocking Changes
Structural & Mocking Changes
Anonymous Class Mocks:
Introduces helper methods (
createEntityMock
,
createEntityMockWithException
) that utilize anonymous classes implementing the
Searchable
interface to simulate different entity states cleanly.
Cursor Simulation:
Mocks the
EntityQueryBuilder
to return an
ArrayIterator
via a
cursor()
method, mimicking the streaming behavior of a database query.
Sentry Facade Swapping:
Implements a
SentryMock
to intercept and assert exception handling logic without triggering actual Sentry events during test execution.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Unnamed Group","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20818 move ask jiminny reports to its own datadog metric by LakyLak · Pull Request #12056 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20818 move ask jiminny reports to its own datadog metric by LakyLak · Pull Request #12056 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Ask Jiminny Report Generated","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Ask Jiminny Report Generated","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Search the CRM - HubSpot docs","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search the CRM - HubSpot docs","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"AI Features | Datadog","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"AI Features | Datadog","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20493 smart instant nudge pre filtering by nikolaybiaivanov · Pull Request #12053 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20493 smart instant nudge pre filtering by nikolaybiaivanov · Pull Request #12053 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.0013888889,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Chat","depth":12,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>Jy 20820 es reindex stream model hydration by Vasi</tabTitle>” with “<selection>@@ -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \\RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \\ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>Jy 20820 es reindex stream model hydration by Vasi</tabTitle>” with “<selection>@@ -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \\RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \\ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This selection contains unit tests for the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"LoadDocumentsAction","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"class, specifically focusing on the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method. The tests verify how the system hydrates search engine document models from database entities, handling updates, deletions, and exceptions.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Core Functionality Tested","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Core Functionality Tested","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The tests use a mocked","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EntityQueryBuilder","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and anonymous","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searchable","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"classes to simulate different hydration scenarios:","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Document Updates:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Verifies that active entities are correctly transformed into","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Document","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"objects containing the expected ID and data (indexable attributes).","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Document Deletions:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ensures that entities flagged as","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"isDeleted","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"are processed for removal from the search index.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed Results:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Validates the ability to process a collection containing both updateable and deletable entities in a single stream.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Empty Result Sets:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Confirms the system handles empty entity lists gracefully without errors.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Error Handling & Resilience","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Error Handling & Resilience","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Exception Catching:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"A specific test case (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") ensures that if a single entity fails to hydrate due to an exception, the system:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Logs the error to","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sentry","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"using a","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncActivityException","depth":29,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Continues processing other valid entities in the stream rather than failing the entire batch.","depth":28,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Technical Implementation Details","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Technical Implementation Details","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mocking Strategy:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Uses","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createMock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for dependencies and a custom","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createEntityMock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"helper to generate anonymous classes implementing the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searchable","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"interface.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Assertions:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Utilizes PHPUnit assertions like","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"assertEquals","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"assertInstanceOf","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"count","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to verify the state of the returned result object.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Iterator Usage:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createQueryBuilderMock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"uses an","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ArrayIterator","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to simulate a database cursor, reflecting the streaming nature of the hydration process.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection. Instructions: Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language. Highlight key changes: Break down the structural or logical changes made in the code. Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs. Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.","depth":21,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Instructions:","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Highlight key changes: Break down the structural or logical changes made in the code.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.","depth":23,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":20,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Core Objective","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Core Objective","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This selection introduces a comprehensive suite of PHPUnit tests for the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"LoadDocumentsAction","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"class. The goal is to verify the logic of the","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"loadDocuments","depth":25,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method, ensuring it correctly transforms streamed database entities into search-indexable documents, accurately segregates them into \"update\" and \"delete\" queues, and handles individual hydration failures without crashing the entire batch process.","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Key Behaviors Verified","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Key Behaviors Verified","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Standard Hydration:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Confirms that active entities (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"isDeleted = false","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") are properly parsed into","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Document","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"objects containing the correct IDs and indexable attributes.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deletion Handling:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ensures entities flagged as deleted (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"isDeleted = true","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") are added to the deletion queue and excluded from the update queue.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mixed Batch Processing:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Validates the system's ability to process a single stream containing both active and deleted entities, successfully splitting them into their respective queues.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Empty Result Sets:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Verifies that providing an empty list of entity IDs resolves gracefully, returning zero documents to update or delete without error.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fault Tolerance:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ensures that if a specific entity throws an exception during attribute generation, the error is logged to Sentry (as a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SyncActivityException","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"), and the system successfully continues processing the remaining valid entities.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Structural & Mocking Changes","depth":23,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Structural & Mocking Changes","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Anonymous Class Mocks:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Introduces helper methods (","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createEntityMock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createEntityMockWithException","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") that utilize anonymous classes implementing the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Searchable","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"interface to simulate different entity states cleanly.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Cursor Simulation:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mocks the","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"EntityQueryBuilder","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to return an","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ArrayIterator","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"via a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cursor()","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"method, mimicking the streaming behavior of a database query.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sentry Facade Swapping:","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Implements a","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SentryMock","depth":27,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to intercept and assert exception handling logic without triggering actual Sentry events during test execution.","depth":26,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Good response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bad response","depth":22,"on_screen":false,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share & export","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show more options","depth":21,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8947955963308997102
|
-3799236680972561530
|
click
|
accessibility
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Unnamed Group
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS] -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>Jy 20820 es reindex stream model hydration by Vasi</tabTitle>” with “<selection>@@ -0,0 +1,266 @@1+queryBuilder = $this->createMock(EntityQueryBuilder::class);29+$this->action = new LoadDocumentsAction($this->queryBuilder);30+ }31+32+public function testLoadDocumentsWithEntitiesToUpdate(): void33+ {34+$entityIds = [1, 2, 3];35+$updateTarget = UpdateTarget::ACTIVITY;36+37+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);38+$entity2 = $this->createEntityMock(2, false, ['field2' => 'value2']);39+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);40+41+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3]);42+43+$this->queryBuilder44+ ->expects($this->once())45+ ->method('getEntityQuery')46+ ->with($updateTarget, $entityIds)47+ ->willReturn($queryBuilder);48+49+$result = $this->action->loadDocuments($updateTarget, $entityIds);50+51+$this->assertEquals(3, $result->getDocumentsToUpdate()->count());52+$this->assertEquals(0, $result->getDocumentsToDelete()->count());53+54+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();55+$this->assertInstanceOf(Document::class, $documentsToUpdate[0]);56+$this->assertEquals('1', $documentsToUpdate[0]->getId());57+$this->assertEquals(['field1' => 'value1'], $documentsToUpdate[0]->getData());58+ }59+60+public function testLoadDocumentsWithEntitiesToDelete(): void61+ {62+$entityIds = [1, 2];63+$updateTarget = UpdateTarget::OPPORTUNITY;64+65+$entity1 = $this->createEntityMock(1, true, []);66+$entity2 = $this->createEntityMock(2, true, []);67+68+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);69+70+$this->queryBuilder71+ ->expects($this->once())72+ ->method('getEntityQuery')73+ ->with($updateTarget, $entityIds)74+ ->willReturn($queryBuilder);75+76+$result = $this->action->loadDocuments($updateTarget, $entityIds);77+78+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());79+$this->assertEquals(2, $result->getDocumentsToDelete()->count());80+81+$documentsToDelete = $result->getDocumentsToDelete()->toArray();82+$this->assertEquals(1, $documentsToDelete[0]);83+$this->assertEquals(2, $documentsToDelete[1]);84+ }85+86+public function testLoadDocumentsWithMixedEntities(): void87+ {88+$entityIds = [1, 2, 3, 4];89+$updateTarget = UpdateTarget::ACTIVITY;90+91+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);92+$entity2 = $this->createEntityMock(2, true, []);93+$entity3 = $this->createEntityMock(3, false, ['field3' => 'value3']);94+$entity4 = $this->createEntityMock(4, true, []);95+96+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2, $entity3, $entity4]);97+98+$this->queryBuilder99+ ->expects($this->once())100+ ->method('getEntityQuery')101+ ->with($updateTarget, $entityIds)102+ ->willReturn($queryBuilder);103+104+$result = $this->action->loadDocuments($updateTarget, $entityIds);105+106+$this->assertEquals(2, $result->getDocumentsToUpdate()->count());107+$this->assertEquals(2, $result->getDocumentsToDelete()->count());108+109+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();110+$this->assertEquals('1', $documentsToUpdate[0]->getId());111+$this->assertEquals('3', $documentsToUpdate[1]->getId());112+113+$documentsToDelete = $result->getDocumentsToDelete()->toArray();114+$this->assertEquals(2, $documentsToDelete[0]);115+$this->assertEquals(4, $documentsToDelete[1]);116+ }117+118+public function testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes(): void119+ {120+$entityIds = [1, 2];121+$updateTarget = UpdateTarget::ACTIVITY;122+123+$entity1 = $this->createEntityMock(1, false, ['field1' => 'value1']);124+$entity2 = $this->createEntityMockWithException(2, false);125+126+$queryBuilder = $this->createQueryBuilderMock([$entity1, $entity2]);127+128+$this->queryBuilder129+ ->expects($this->once())130+ ->method('getEntityQuery')131+ ->with($updateTarget, $entityIds)132+ ->willReturn($queryBuilder);133+134+$sentryMock = $this->createMock(SentryMock::class);135+$sentryMock->expects($this->once())136+ ->method('captureException')137+ ->with($this->isInstanceOf(SyncActivityException::class));138+139+ Sentry::swap($sentryMock);140+141+142+$result = $this->action->loadDocuments($updateTarget, $entityIds);143+144+$this->assertEquals(1, $result->getDocumentsToUpdate()->count());145+$this->assertEquals(0, $result->getDocumentsToDelete()->count());146+147+$documentsToUpdate = $result->getDocumentsToUpdate()->toArray();148+$this->assertEquals('1', $documentsToUpdate[0]->getId());149+ }150+151+public function testLoadDocumentsWithEmptyEntityList(): void152+ {153+$entityIds = [];154+$updateTarget = UpdateTarget::ACTIVITY;155+156+$queryBuilder = $this->createQueryBuilderMock([]);157+158+$this->queryBuilder159+ ->expects($this->once())160+ ->method('getEntityQuery')161+ ->with($updateTarget, $entityIds)162+ ->willReturn($queryBuilder);163+164+$result = $this->action->loadDocuments($updateTarget, $entityIds);165+166+$this->assertEquals(0, $result->getDocumentsToUpdate()->count());167+$this->assertEquals(0, $result->getDocumentsToDelete()->count());168+ }169+170+private function createEntityMock(int $id, bool $isDeleted, array $indexableAttributes): Searchable171+ {172+return new class ($id, $isDeleted, $indexableAttributes) implements Searchable {173+public function __construct(174+private int $id,175+private bool $isDeleted,176+private array $indexableAttributes177+ ) {178+ }179+180+public function getId(): int181+ {182+return $this->id;183+ }184+185+public function getIndex(): string186+ {187+return 'test_index';188+ }189+190+public function getSearchKey(): int191+ {192+return $this->id;193+ }194+195+public function getIndexableAttributes(): array196+ {197+return $this->indexableAttributes;198+ }199+200+public function isDeleted(): bool201+ {202+return $this->isDeleted;203+ }204+205+public function setRelations(array $relations): void206+ {207+ }208+ };209+ }210+211+private function createEntityMockWithException(int $id, bool $isDeleted): Searchable212+ {213+return new class ($id, $isDeleted) implements Searchable {214+public function __construct(215+private int $id,216+private bool $isDeleted217+ ) {218+ }219+220+public function getId(): int221+ {222+return $this->id;223+ }224+225+public function getIndex(): string226+ {227+return 'test_index';228+ }229+230+public function getSearchKey(): int231+ {232+return $this->id;233+ }234+235+public function getIndexableAttributes(): array236+ {237+throw new \RuntimeException('Failed to build indexable attributes');238+ }239+240+public function isDeleted(): bool241+ {242+return $this->isDeleted;243+ }244+245+public function setRelations(array $relations): void246+ {247+ }248+ };249+ }250+251+private function createQueryBuilderMock(array $entities): Builder&MockObject252+ {253+$queryBuilder = $this->createMock(Builder::class);254+$queryBuilder->method('cursor')->willReturn(new \ArrayIterator($entities));255+256+return $queryBuilder;257+ }258+}259+260+class SentryMock261+{262+public function captureException(Exception $e)263+ {264+// Do nothing.265+ }266+}</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Gemini said
Gemini said
This selection contains unit tests for the
LoadDocumentsAction
class, specifically focusing on the
loadDocuments
method. The tests verify how the system hydrates search engine document models from database entities, handling updates, deletions, and exceptions.
Core Functionality Tested
Core Functionality Tested
The tests use a mocked
EntityQueryBuilder
and anonymous
Searchable
classes to simulate different hydration scenarios:
Document Updates:
Verifies that active entities are correctly transformed into
Document
objects containing the expected ID and data (indexable attributes).
Document Deletions:
Ensures that entities flagged as
isDeleted
are processed for removal from the search index.
Mixed Results:
Validates the ability to process a collection containing both updateable and deletable entities in a single stream.
Empty Result Sets:
Confirms the system handles empty entity lists gracefully without errors.
Error Handling & Resilience
Error Handling & Resilience
Exception Catching:
A specific test case (
testLoadDocumentsHandlesExceptionWhenBuildingIndexableAttributes
) ensures that if a single entity fails to hydrate due to an exception, the system:
Logs the error to
Sentry
using a
SyncActivityException
.
Continues processing other valid entities in the stream rather than failing the entire batch.
Technical Implementation Details
Technical Implementation Details
Mocking Strategy:
Uses
createMock
for dependencies and a custom
createEntityMock
helper to generate anonymous classes implementing the
Searchable
interface.
Assertions:
Utilizes PHPUnit assertions like
assertEquals
,
assertInstanceOf
, and
count
to verify the state of the returned result object.
Iterator Usage:
The
createQueryBuilderMock
uses an
ArrayIterator
to simulate a database cursor, reflecting the streaming nature of the hydration process.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt
You said Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection. Instructions: Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language. Highlight key changes: Break down the structural or logical changes made in the code. Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs. Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.
You said
Role: You are a Technical Lead. Your task is to provide a clear, high-level summary of the provided code diff or PR selection.
Instructions:
Summarize the core objective: Explain what the code is doing and why (if apparent from the context) using precise and concise language.
Highlight key changes: Break down the structural or logical changes made in the code.
Formatting: Use headers and bulleted lists to make the summary highly scannable for a developer. Avoid long paragraphs.
Accuracy: Maintain the exact meaning and factual accuracy of the provided code without hallucinating outside context.
Expand
Listen
Show more options
Show thinking
Gemini said
Gemini said
Core Objective
Core Objective
This selection introduces a comprehensive suite of PHPUnit tests for the
LoadDocumentsAction
class. The goal is to verify the logic of the
loadDocuments
method, ensuring it correctly transforms streamed database entities into search-indexable documents, accurately segregates them into "update" and "delete" queues, and handles individual hydration failures without crashing the entire batch process.
Key Behaviors Verified
Key Behaviors Verified
Standard Hydration:
Confirms that active entities (
isDeleted = false
) are properly parsed into
Document
objects containing the correct IDs and indexable attributes.
Deletion Handling:
Ensures entities flagged as deleted (
isDeleted = true
) are added to the deletion queue and excluded from the update queue.
Mixed Batch Processing:
Validates the system's ability to process a single stream containing both active and deleted entities, successfully splitting them into their respective queues.
Empty Result Sets:
Verifies that providing an empty list of entity IDs resolves gracefully, returning zero documents to update or delete without error.
Fault Tolerance:
Ensures that if a specific entity throws an exception during attribute generation, the error is logged to Sentry (as a
SyncActivityException
), and the system successfully continues processing the remaining valid entities.
Structural & Mocking Changes
Structural & Mocking Changes
Anonymous Class Mocks:
Introduces helper methods (
createEntityMock
,
createEntityMockWithException
) that utilize anonymous classes implementing the
Searchable
interface to simulate different entity states cleanly.
Cursor Simulation:
Mocks the
EntityQueryBuilder
to return an
ArrayIterator
via a
cursor()
method, mimicking the streaming behavior of a database query.
Sentry Facade Swapping:
Implements a
SentryMock
to intercept and assert exception handling logic without triggering actual Sentry events during test execution.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
913
|
32
|
31
|
2026-05-07T07:41:37.597611+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778139697597_m2.jpg...
|
Claude
|
Claude
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Open sidebar
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Definition of incarcerated
More options for Definition of incarcerated
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Salesforce API errors with Organization and FieldDefinition queries
More options for Salesforce API errors with Organization and FieldDefinition queries
Daily activity summary from screenpipe data
More options for Daily activity summary from screenpipe data
MacBook unexpected restarts and kanji screen
More options for MacBook unexpected restarts and kanji screen
Security patch review and testing guidance
More options for Security patch review and testing guidance
Food calorie values reference
More options for Food calorie values reference
Tracking location history from last week
More options for Tracking location history from last week
Screenpipe WAL processing when stopped
More options for Screenpipe WAL processing when stopped
Lukas Pro
Get apps and extensions
Coffee and Claude time?
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
• updating activity (engagement, meeting, call, note)
• searching for a company or contact
• updating deal
• patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
• where to store it
• how to read it
• how to work with multiple jobs
• how to work with paginated requests
Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? API usage guidelines and limits - HubSpot docs
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
updating activity (engagement, meeting, call, note)
searching for a company or contact
updating deal
patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
where to store it
how to read it
how to work with multiple jobs
how to work with paginated requests
Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues?
API usage guidelines and limits - HubSpot docs
API usage guidelines and limits - HubSpot docs
Add files, connectors, and more
Model: Sonnet 4.6
Sonnet 4.6
Send message
Use incognito...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"bounds":{"left":0.029587766,"top":0.03830806,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"bounds":{"left":0.10239362,"top":0.06703911,"width":0.030585106,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10239362,"top":0.06703911,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":16,"bounds":{"left":0.10538564,"top":0.06703911,"width":0.027925532,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"bounds":{"left":0.1349734,"top":0.06703911,"width":0.0063164895,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"bounds":{"left":0.10239362,"top":0.079010375,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10239362,"top":0.079010375,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.10538564,"top":0.079010375,"width":0.022938829,"height":0.011971269}}],"role_description":"text"},{"role":"AXButton","text":"Open sidebar","depth":14,"bounds":{"left":0.029920213,"top":0.02793296,"width":0.00930851,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"bounds":{"left":0.004986702,"top":0.059856344,"width":0.025930852,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"bounds":{"left":0.03158245,"top":0.059856344,"width":0.03125,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"bounds":{"left":0.0631649,"top":0.059856344,"width":0.026928192,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":15,"bounds":{"left":0.0043218085,"top":0.08938547,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":16,"bounds":{"left":0.014295213,"top":0.0933759,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.014295213,"top":0.0933759,"width":0.003656915,"height":0.013567438}},{"char_start":1,"char_count":7,"bounds":{"left":0.01761968,"top":0.0933759,"width":0.015957447,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":17,"bounds":{"left":0.08178192,"top":0.0933759,"width":0.006981383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Projects","depth":15,"bounds":{"left":0.0043218085,"top":0.110135674,"width":0.08643617,"height":0.019952115},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":15,"bounds":{"left":0.0043218085,"top":0.1300878,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":15,"bounds":{"left":0.0043218085,"top":0.15003991,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"bounds":{"left":0.0063164895,"top":0.18914606,"width":0.08377659,"height":0.013567438},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":18,"bounds":{"left":0.0043218085,"top":0.20590582,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":19,"bounds":{"left":0.08344415,"top":0.20909816,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":18,"bounds":{"left":0.0043218085,"top":0.22745411,"width":0.08643617,"height":0.019952115},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":19,"bounds":{"left":0.08344415,"top":0.22984837,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"bounds":{"left":0.0063164895,"top":0.25698325,"width":0.06349734,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"bounds":{"left":0.07114362,"top":0.25698325,"width":0.018949468,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe retention policy code location","depth":18,"bounds":{"left":0.0043218085,"top":0.27294493,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe retention policy code location","depth":19,"bounds":{"left":0.08344415,"top":0.27613726,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Viewing retention policy in screenpipe","depth":18,"bounds":{"left":0.0043218085,"top":0.29449323,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Viewing retention policy in screenpipe","depth":19,"bounds":{"left":0.08344415,"top":0.29768556,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clean shot x video recording termination issue","depth":18,"bounds":{"left":0.0043218085,"top":0.31524342,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Clean shot x video recording termination issue","depth":19,"bounds":{"left":0.08344415,"top":0.31843576,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit handling with executeRequest","depth":18,"bounds":{"left":0.0043218085,"top":0.3367917,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit handling with executeRequest","depth":19,"bounds":{"left":0.08344415,"top":0.33998403,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Untitled","depth":18,"bounds":{"left":0.0043218085,"top":0.3575419,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options","depth":19,"bounds":{"left":0.08344415,"top":0.36073422,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 Screen pipe. Is there ability…","depth":18,"bounds":{"left":0.0043218085,"top":0.3790902,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 Screen pipe. Is there ability…","depth":19,"bounds":{"left":0.08344415,"top":0.38228253,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"SMB mount access inconsistency between Finder and iTerm","depth":18,"bounds":{"left":0.0043218085,"top":0.39984038,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for SMB mount access inconsistency between Finder and iTerm","depth":19,"bounds":{"left":0.08344415,"top":0.40303272,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 What is the best switch I can…","depth":18,"bounds":{"left":0.0043218085,"top":0.42138866,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 What is the best switch I can…","depth":19,"bounds":{"left":0.08344415,"top":0.4237829,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Permission denied on screenpipe volume","depth":18,"bounds":{"left":0.0043218085,"top":0.44213888,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Permission denied on screenpipe volume","depth":19,"bounds":{"left":0.08344415,"top":0.44533122,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync database attachment error","depth":18,"bounds":{"left":0.0043218085,"top":0.46288908,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync database attachment error","depth":19,"bounds":{"left":0.08344415,"top":0.4660814,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Last swimming outing with Dani","depth":18,"bounds":{"left":0.0043218085,"top":0.48443735,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Last swimming outing with Dani","depth":19,"bounds":{"left":0.08344415,"top":0.48762968,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Definition of incarcerated","depth":18,"bounds":{"left":0.0043218085,"top":0.5051876,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Definition of incarcerated","depth":19,"bounds":{"left":0.08344415,"top":0.5083799,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chromecast remote volume buttons not working","depth":18,"bounds":{"left":0.0043218085,"top":0.52673584,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Chromecast remote volume buttons not working","depth":19,"bounds":{"left":0.08344415,"top":0.52992815,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Salesforce API errors with Organization and FieldDefinition queries","depth":18,"bounds":{"left":0.0043218085,"top":0.547486,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Salesforce API errors with Organization and FieldDefinition queries","depth":19,"bounds":{"left":0.08344415,"top":0.5506784,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Daily activity summary from screenpipe data","depth":18,"bounds":{"left":0.0043218085,"top":0.56903434,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Daily activity summary from screenpipe data","depth":19,"bounds":{"left":0.08344415,"top":0.57222664,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"MacBook unexpected restarts and kanji screen","depth":18,"bounds":{"left":0.0043218085,"top":0.5897845,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for MacBook unexpected restarts and kanji screen","depth":19,"bounds":{"left":0.08344415,"top":0.59297687,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Security patch review and testing guidance","depth":18,"bounds":{"left":0.0043218085,"top":0.6113328,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Security patch review and testing guidance","depth":19,"bounds":{"left":0.08344415,"top":0.61452514,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Food calorie values reference","depth":18,"bounds":{"left":0.0043218085,"top":0.632083,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Food calorie values reference","depth":19,"bounds":{"left":0.08344415,"top":0.63527536,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tracking location history from last week","depth":18,"bounds":{"left":0.0043218085,"top":0.65363127,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Tracking location history from last week","depth":19,"bounds":{"left":0.08344415,"top":0.65682364,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe WAL processing when stopped","depth":18,"bounds":{"left":0.0043218085,"top":0.6743815,"width":0.08643617,"height":0.011173184},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe WAL processing when stopped","depth":19,"bounds":{"left":0.08344415,"top":0.6775738,"width":0.005984043,"height":0.007980846},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Pro","depth":15,"bounds":{"left":0.0043218085,"top":0.6943336,"width":0.037898935,"height":0.01915403},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Get apps and extensions","depth":15,"bounds":{"left":0.08277926,"top":0.6943336,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Coffee and Claude time?","depth":18,"bounds":{"left":0.18550532,"top":0.3056664,"width":0.1462766,"height":0.040702313},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.18583776,"top":0.3064645,"width":0.010305851,"height":0.03990423}},{"char_start":1,"char_count":22,"bounds":{"left":0.19614361,"top":0.3064645,"width":0.1356383,"height":0.03990423}}],"role_description":"text"},{"role":"AXTextArea","text":"So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:\n• updating activity (engagement, meeting, call, note)\n• searching for a company or contact\n• updating deal\n• patching IDs\nReview what the limit is at the moment in the HubSpot documentation and then think of the solution:\n• where to store it\n• how to read it\n• how to work with multiple jobs\n• how to work with paginated requests \nLet's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? API usage guidelines and limits - HubSpot docs","depth":22,"bounds":{"left":0.14660904,"top":0.39185953,"width":0.21210106,"height":0.28651237},"on_screen":true,"value":"So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:\n• updating activity (engagement, meeting, call, note)\n• searching for a company or contact\n• updating deal\n• patching IDs\nReview what the limit is at the moment in the HubSpot documentation and then think of the solution:\n• where to store it\n• how to read it\n• how to work with multiple jobs\n• how to work with paginated requests \nLet's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? API usage guidelines and limits - HubSpot docs","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:","depth":24,"bounds":{"left":0.14660904,"top":0.3926576,"width":0.20611702,"height":0.0518755},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.14660904,"top":0.3934557,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":247,"bounds":{"left":0.14660904,"top":0.3934557,"width":0.20611702,"height":0.0518755}}],"role_description":"text"},{"role":"AXStaticText","text":"updating activity (engagement, meeting, call, note)","depth":26,"bounds":{"left":0.15724733,"top":0.4461293,"width":0.12666224,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.44692737,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":50,"bounds":{"left":0.16057181,"top":0.44692737,"width":0.123005316,"height":0.015961692}}],"role_description":"text"},{"role":"AXStaticText","text":"searching for a company or contact","depth":26,"bounds":{"left":0.15724733,"top":0.46448523,"width":0.087765954,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.46448523,"width":0.0026595744,"height":0.016759777}},{"char_start":1,"char_count":33,"bounds":{"left":0.15990691,"top":0.46448523,"width":0.08510638,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"updating deal","depth":26,"bounds":{"left":0.15724733,"top":0.4820431,"width":0.033909574,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.4828412,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":12,"bounds":{"left":0.16057181,"top":0.4828412,"width":0.030917553,"height":0.015961692}}],"role_description":"text"},{"role":"AXStaticText","text":"patching IDs","depth":26,"bounds":{"left":0.15724733,"top":0.49960095,"width":0.03125,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.50039905,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":11,"bounds":{"left":0.16057181,"top":0.50039905,"width":0.027925532,"height":0.015961692}}],"role_description":"text"},{"role":"AXStaticText","text":"Review what the limit is at the moment in the HubSpot documentation and then think of the solution:","depth":24,"bounds":{"left":0.14660904,"top":0.5179569,"width":0.19847074,"height":0.03431764},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.14660904,"top":0.5179569,"width":0.003656915,"height":0.016759777}},{"char_start":1,"char_count":98,"bounds":{"left":0.14660904,"top":0.5179569,"width":0.19847074,"height":0.03431764}}],"role_description":"text"},{"role":"AXStaticText","text":"where to store it","depth":26,"bounds":{"left":0.15724733,"top":0.55387074,"width":0.041223403,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.55387074,"width":0.0043218085,"height":0.016759777}},{"char_start":1,"char_count":16,"bounds":{"left":0.16156915,"top":0.55387074,"width":0.036901597,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"how to read it","depth":26,"bounds":{"left":0.15724733,"top":0.5714286,"width":0.034242023,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.57222664,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":13,"bounds":{"left":0.16057181,"top":0.57222664,"width":0.030917553,"height":0.015961692}}],"role_description":"text"},{"role":"AXStaticText","text":"how to work with multiple jobs","depth":26,"bounds":{"left":0.15724733,"top":0.58898646,"width":0.076130316,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.5897845,"width":0.0033244682,"height":0.015961692}},{"char_start":1,"char_count":29,"bounds":{"left":0.16057181,"top":0.5897845,"width":0.07280585,"height":0.015961692}}],"role_description":"text"},{"role":"AXStaticText","text":"how to work with paginated requests","depth":26,"bounds":{"left":0.15724733,"top":0.60734236,"width":0.09242021,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.15724733,"top":0.60734236,"width":0.0033244682,"height":0.016759777}},{"char_start":1,"char_count":34,"bounds":{"left":0.16057181,"top":0.60734236,"width":0.08909574,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues?","depth":24,"bounds":{"left":0.14660904,"top":0.6249002,"width":0.2081117,"height":0.052673582},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.14660904,"top":0.6256983,"width":0.0013297872,"height":0.015961692}},{"char_start":1,"char_count":187,"bounds":{"left":0.14660904,"top":0.6256983,"width":0.2081117,"height":0.0518755}}],"role_description":"text"},{"role":"AXLink","text":"API usage guidelines and limits - HubSpot docs","depth":24,"bounds":{"left":0.2087766,"top":0.66081405,"width":0.11735372,"height":0.016759777},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"API usage guidelines and limits - HubSpot docs","depth":25,"bounds":{"left":0.2087766,"top":0.66081405,"width":0.11735372,"height":0.016759777},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.2087766,"top":0.66161215,"width":0.0043218085,"height":0.015961692}},{"char_start":1,"char_count":45,"bounds":{"left":0.21276596,"top":0.66161215,"width":0.11336436,"height":0.015961692}}],"role_description":"text"},{"role":"AXPopUpButton","text":"Add files, connectors, and more","depth":24,"bounds":{"left":0.14527926,"top":0.68715084,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Model: Sonnet 4.6","depth":24,"bounds":{"left":0.31083778,"top":0.68715084,"width":0.034574468,"height":0.026336791},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Sonnet 4.6","depth":26,"bounds":{"left":0.31416222,"top":0.6927374,"width":0.023271276,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.31416222,"top":0.6935355,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.3168218,"top":0.6935355,"width":0.020611702,"height":0.014365523}}],"role_description":"text"},{"role":"AXButton","text":"Send message","depth":23,"bounds":{"left":0.3480718,"top":0.68715084,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Use incognito","depth":19,"bounds":{"left":0.48537233,"top":0.02793296,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8947685726944183293
|
-3292590129427945316
|
visual_change
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Open sidebar
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Definition of incarcerated
More options for Definition of incarcerated
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Salesforce API errors with Organization and FieldDefinition queries
More options for Salesforce API errors with Organization and FieldDefinition queries
Daily activity summary from screenpipe data
More options for Daily activity summary from screenpipe data
MacBook unexpected restarts and kanji screen
More options for MacBook unexpected restarts and kanji screen
Security patch review and testing guidance
More options for Security patch review and testing guidance
Food calorie values reference
More options for Food calorie values reference
Tracking location history from last week
More options for Tracking location history from last week
Screenpipe WAL processing when stopped
More options for Screenpipe WAL processing when stopped
Lukas Pro
Get apps and extensions
Coffee and Claude time?
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
• updating activity (engagement, meeting, call, note)
• searching for a company or contact
• updating deal
• patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
• where to store it
• how to read it
• how to work with multiple jobs
• how to work with paginated requests
Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? API usage guidelines and limits - HubSpot docs
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
updating activity (engagement, meeting, call, note)
searching for a company or contact
updating deal
patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
where to store it
how to read it
how to work with multiple jobs
how to work with paginated requests
Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues?
API usage guidelines and limits - HubSpot docs
API usage guidelines and limits - HubSpot docs
Add files, connectors, and more
Model: Sonnet 4.6
Sonnet 4.6
Send message
Use incognito...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
18012
|
783
|
30
|
2026-05-11T10:45:15.897583+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778496315897_m2.jpg...
|
Notion Calendar
|
NULL
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
ClaudecaltVIewWindowHubspot BadRequest headers deb ClaudecaltVIewWindowHubspot BadRequest headers debugging0, Chat*= Cowork" Coae+ New chatã Projectso0 Arutacts₴ CustomizeBulgarian citizenship application proces:Dawarich location tracking projectHubspot sadkequest headers debuggingScreenpipe svnc script tailing after receiMonthly expense trackingExporting transaction data from Notion t® How much have I spent for groc...April 2026 spending by categoryCode dift reviewHubSpot rate limit imolementation strateScreenpipe retention policy code locatioViewing retention policy in screenpipeClean snot x video recording terminaticHubSpot rate limit handling with executeUntitledaScreen oine. Is there ability.SMB mount access inconsistency betweWhat is the best switch I can...rermission deniea on screenpipe volumeScreenpipe sync database attachment eLast swimming outing with DanDefinition of incarceratedIn the HubSpot PHP SDK. BadRequest comes through as an ApiExcention. It exposes theresponse headers via getResponseHeaders() :use Hubspot cllent crm contacts Aplexception:try($response = $hubspot->crm()->contacts()->basicApi()->create($input);3 catch (ApiException $e) {$status = $e->getCodeO:400the errorQ Search chats and projectsHealth• How repairs⅝ Paramedie© Screenpipe sync script failing after recent migrations- Monthly expense trackingYesterday?? null,-Remaining'J ?? null,condly-Remaining'] ??© Exporting transaction data from Notion to finance hubYesterday• • How much have I spent for groc..Past wee!April 2026 spending by categoryPast weekay be arrays (one© Code diff reviewPast week© HubSpot rate limit implementation strategyPast week? null)@ Screenpine retention policy code locationPast week© Viewing retention policy in screenpipePast monthThe X-HubSpot-Correlation-Id is the one to grab if you need to open a ticket with HubSpot— they'll ask for it.If you're using Guzzle directly instead of the SDK, it's $e->getResponse () ->getHeaders() ona RequestExcention/ Cuientsxcention.Write a message…Opus 4.7 Adaptive vlK Lukas. ProClaude is Al and can make mistakes. Please double-check responses.t/Service.php© PaginationConfig.phpA3 A70 V3 A$0"suppont Dally • In 1h 1om100% Lz&• Mon 11 May 13:45:15= custom.log xE laravel.log4 SF [jiminny@localhost]A HS_Jocal (jiminny@localhost][2026-05-07 14:21:15] Local. INFO: [Hubspot] DEBUG Getting headers €"neaders".?"Date": ["Thu,07 May 2026 14:21:15 GMT"],"Content-Type":["application/json;charset=utf-8"]."Transfer-Encoding": ["'chunked"],"conneccion". Keep-alive"n"CF-Ray" : ["9f80deb8db60dc3a-SOF"],"CF-Cache-Status":"DYNAMIC"]"Strict-Transport-Secur1ty":"max-aqe=31536000* 1ncLudesubDomains: preload")A console [PROD]A console (EU]4 console (STAGING]w.19Aaccept-encoding"],"access-control-allow-credentials": "false")"server-timing": ["heid; desc=|"019e02d0-6fd8-7812-bdba-885b7ecb3ee3!",cfn:desc="9-80de8ercodc3a-TAD"'"x-content-type-options": I"nosnitf"],"x-hubsoot-correlation-id":"019e02d0-6fd8-7812-bdba-885b7ccb3ee3"]"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEs0.07-May-26 14:51:15 GMT; domain=.hubapi.com; Http0nly; Secure; SameSite=None"],"Report-To": ["{\"endpoints)":[\"urZ\":\"https:|\/I\/a.nel.cloudflare.com\\/report\\/v4?s=NYALsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZ1zoYdxI%2BIxVpHmsKn30%2BKVA3mFIJ2m7YRECDGSJ\"groupl":\"cf-nel\",\"max_age\":604800}"],\"success_fraction\":0.01,("'report_to\":\"cf-nel\",\"max_age\":604800}"],"Server": ["cLoudflare"]}} {"correlation_id": "95236535-ec98-4541-b92a-adfa73b69eab","trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}1 MT 1T1IINW Windsurf Teams 20-17UTF.Rf 4 spaces ©...
|
NULL
|
8946832865849889161
|
NULL
|
click
|
ocr
|
NULL
|
ClaudecaltVIewWindowHubspot BadRequest headers deb ClaudecaltVIewWindowHubspot BadRequest headers debugging0, Chat*= Cowork" Coae+ New chatã Projectso0 Arutacts₴ CustomizeBulgarian citizenship application proces:Dawarich location tracking projectHubspot sadkequest headers debuggingScreenpipe svnc script tailing after receiMonthly expense trackingExporting transaction data from Notion t® How much have I spent for groc...April 2026 spending by categoryCode dift reviewHubSpot rate limit imolementation strateScreenpipe retention policy code locatioViewing retention policy in screenpipeClean snot x video recording terminaticHubSpot rate limit handling with executeUntitledaScreen oine. Is there ability.SMB mount access inconsistency betweWhat is the best switch I can...rermission deniea on screenpipe volumeScreenpipe sync database attachment eLast swimming outing with DanDefinition of incarceratedIn the HubSpot PHP SDK. BadRequest comes through as an ApiExcention. It exposes theresponse headers via getResponseHeaders() :use Hubspot cllent crm contacts Aplexception:try($response = $hubspot->crm()->contacts()->basicApi()->create($input);3 catch (ApiException $e) {$status = $e->getCodeO:400the errorQ Search chats and projectsHealth• How repairs⅝ Paramedie© Screenpipe sync script failing after recent migrations- Monthly expense trackingYesterday?? null,-Remaining'J ?? null,condly-Remaining'] ??© Exporting transaction data from Notion to finance hubYesterday• • How much have I spent for groc..Past wee!April 2026 spending by categoryPast weekay be arrays (one© Code diff reviewPast week© HubSpot rate limit implementation strategyPast week? null)@ Screenpine retention policy code locationPast week© Viewing retention policy in screenpipePast monthThe X-HubSpot-Correlation-Id is the one to grab if you need to open a ticket with HubSpot— they'll ask for it.If you're using Guzzle directly instead of the SDK, it's $e->getResponse () ->getHeaders() ona RequestExcention/ Cuientsxcention.Write a message…Opus 4.7 Adaptive vlK Lukas. ProClaude is Al and can make mistakes. Please double-check responses.t/Service.php© PaginationConfig.phpA3 A70 V3 A$0"suppont Dally • In 1h 1om100% Lz&• Mon 11 May 13:45:15= custom.log xE laravel.log4 SF [jiminny@localhost]A HS_Jocal (jiminny@localhost][2026-05-07 14:21:15] Local. INFO: [Hubspot] DEBUG Getting headers €"neaders".?"Date": ["Thu,07 May 2026 14:21:15 GMT"],"Content-Type":["application/json;charset=utf-8"]."Transfer-Encoding": ["'chunked"],"conneccion". Keep-alive"n"CF-Ray" : ["9f80deb8db60dc3a-SOF"],"CF-Cache-Status":"DYNAMIC"]"Strict-Transport-Secur1ty":"max-aqe=31536000* 1ncLudesubDomains: preload")A console [PROD]A console (EU]4 console (STAGING]w.19Aaccept-encoding"],"access-control-allow-credentials": "false")"server-timing": ["heid; desc=|"019e02d0-6fd8-7812-bdba-885b7ecb3ee3!",cfn:desc="9-80de8ercodc3a-TAD"'"x-content-type-options": I"nosnitf"],"x-hubsoot-correlation-id":"019e02d0-6fd8-7812-bdba-885b7ccb3ee3"]"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEs0.07-May-26 14:51:15 GMT; domain=.hubapi.com; Http0nly; Secure; SameSite=None"],"Report-To": ["{\"endpoints)":[\"urZ\":\"https:|\/I\/a.nel.cloudflare.com\\/report\\/v4?s=NYALsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZ1zoYdxI%2BIxVpHmsKn30%2BKVA3mFIJ2m7YRECDGSJ\"groupl":\"cf-nel\",\"max_age\":604800}"],\"success_fraction\":0.01,("'report_to\":\"cf-nel\",\"max_age\":604800}"],"Server": ["cLoudflare"]}} {"correlation_id": "95236535-ec98-4541-b92a-adfa73b69eab","trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}1 MT 1T1IINW Windsurf Teams 20-17UTF.Rf 4 spaces ©...
|
18010
|
NULL
|
NULL
|
NULL
|
|
10914
|
489
|
25
|
2026-05-08T18:06:30.217749+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778263590217_m1.jpg...
|
Code
|
docker-compose.yml — docker [SSH: nas]
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
certs
db
media
templates
.env
.env.example
docker-compose.yml
README.md
beszel
bitwarden
dawarich
dsk-uploader
finance
payments-logger
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
Review payment logger au…, Editor Group 2
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports
PORTS
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
collapsed
Command Succeeded
The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Explorer (⇧⌘E)","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Search (⇧⌘F)","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Source Control (⌃⇧G) - 55 pending changes","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"55","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Run and Debug (⇧⌘D)","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Remote Explorer","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Extensions (⇧⌘X) - 2 require update","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":22,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Claude Code","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Containers","depth":19,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"EXPLORER","depth":17,"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"EXPLORER","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Explorer Section: docker [SSH: nas]","depth":21,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Explorer Section: docker [SSH: nas]","depth":22,"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"DOCKER [SSH: NAS]","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"adguard","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-stack","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"app-db","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"appflowy","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"audiobookshelf","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"auth","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"certs","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"db","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"media","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"templates","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"docker-compose.yml","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"README.md","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"beszel","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bitwarden","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dawarich","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dsk-uploader","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"finance","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"payments-logger","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"flask-app","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"garmin-connector","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"gitea","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health-tracker","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"homarr","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"hst","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"immich","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jellyfinht","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"kavita","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"libreoffice","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"linkwarden","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"app","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mcp-server","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".gitignore","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".mcp.json","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic.ini","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"docker-compose.yml","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Dockerfile","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"README.md","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"requirements.txt","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"today_map.html","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mariadb","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"meeting-detector","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mindfulmama","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Outline Section","depth":21,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"OUTLINE","depth":22,"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"OUTLINE","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Timeline Section","depth":21,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"TIMELINE","depth":22,"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"TIMELINE","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","depth":28,"on_screen":true,"value":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","depth":29,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Review payment logger au…, Editor Group 2","depth":28,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"on_screen":false,"role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Problems (⇧⌘M)","depth":22,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"PROBLEMS","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Output (⇧⌘U)","depth":22,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUTPUT","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Debug Console (⇧⌘Y)","depth":22,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DEBUG CONSOLE","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Terminal (⌃`)","depth":22,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"TERMINAL","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Ports","depth":22,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"PORTS","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"remote SSH: nas","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SSH: nas","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git)","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - main*, Checkout Branch/Tag...","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"main*","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - Synchronize Changes","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"No Problems","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"No Ports Forwarded","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Notifications","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sign In","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sign In","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Compose","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"LF","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"UTF-8","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Spaces: 2","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ln 1, Col 1","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"collapsed","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command Succeeded","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination.","depth":16,"on_screen":true,"role_description":"text"}]...
|
8946020387746705504
|
8416343648516981816
|
click
|
accessibility
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
certs
db
media
templates
.env
.env.example
docker-compose.yml
README.md
beszel
bitwarden
dawarich
dsk-uploader
finance
payments-logger
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
Review payment logger au…, Editor Group 2
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports
PORTS
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
collapsed
Command Succeeded
The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination....
|
10913
|
NULL
|
NULL
|
NULL
|
|
10915
|
490
|
24
|
2026-05-08T18:06:30.363104+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778263590363_m2.jpg...
|
Code
|
docker-compose.yml — docker [SSH: nas]
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
certs
db
media
templates
.env
.env.example
docker-compose.yml
README.md
beszel
bitwarden
dawarich
dsk-uploader
finance
payments-logger
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
Review payment logger au…, Editor Group 2
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports
PORTS
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
collapsed
Command Succeeded
The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination....
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Explorer (⇧⌘E)","depth":19,"bounds":{"left":0.0,"top":0.047885075,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.057462092,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Search (⇧⌘F)","depth":19,"bounds":{"left":0.0,"top":0.08619314,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.09577015,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Source Control (⌃⇧G) - 55 pending changes","depth":19,"bounds":{"left":0.0,"top":0.1245012,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.13407822,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"55","depth":22,"bounds":{"left":0.0076462766,"top":0.1452514,"width":0.0043218085,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Run and Debug (⇧⌘D)","depth":19,"bounds":{"left":0.0,"top":0.16280925,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.17238627,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Remote Explorer","depth":19,"bounds":{"left":0.0,"top":0.20111732,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.21069433,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Extensions (⇧⌘X) - 2 require update","depth":19,"bounds":{"left":0.0,"top":0.23942538,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.2490024,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":22,"bounds":{"left":0.009640957,"top":0.2601756,"width":0.0019946808,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Claude Code","depth":19,"bounds":{"left":0.0,"top":0.27773345,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Containers","depth":19,"bounds":{"left":0.0,"top":0.3160415,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"EXPLORER","depth":17,"bounds":{"left":0.022606382,"top":0.047885075,"width":0.018949468,"height":0.02793296},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"EXPLORER","depth":18,"bounds":{"left":0.022606382,"top":0.056664005,"width":0.018949468,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.022606382,"top":0.056664005,"width":0.0023271276,"height":0.0103751}},{"char_start":1,"char_count":7,"bounds":{"left":0.024933511,"top":0.056664005,"width":0.01662234,"height":0.0103751}}],"role_description":"text"},{"role":"AXButton","text":"Explorer Section: docker [SSH: nas]","depth":21,"bounds":{"left":0.015957447,"top":0.07581804,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Explorer Section: docker [SSH: nas]","depth":22,"bounds":{"left":0.022606382,"top":0.07581804,"width":0.038231384,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"DOCKER [SSH: NAS]","depth":23,"bounds":{"left":0.022606382,"top":0.079010375,"width":0.038231384,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.022606382,"top":0.07980846,"width":0.0029920214,"height":0.0103751}},{"char_start":1,"char_count":16,"bounds":{"left":0.025598405,"top":0.07980846,"width":0.03523936,"height":0.0103751}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.09577015,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"adguard","depth":27,"bounds":{"left":0.025930852,"top":0.09577015,"width":0.01662234,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.096568234,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.02825798,"top":0.096568234,"width":0.014295213,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.11332801,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-stack","depth":27,"bounds":{"left":0.025930852,"top":0.11332801,"width":0.016289894,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.11412609,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":7,"bounds":{"left":0.02825798,"top":0.11412609,"width":0.013962766,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.13088587,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"app-db","depth":27,"bounds":{"left":0.025930852,"top":0.13088587,"width":0.014960106,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.13168396,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.02825798,"top":0.13168396,"width":0.012632979,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.14844373,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"appflowy","depth":27,"bounds":{"left":0.025930852,"top":0.14844373,"width":0.018284574,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.14924182,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":7,"bounds":{"left":0.02825798,"top":0.14924182,"width":0.015957447,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.1660016,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"audiobookshelf","depth":27,"bounds":{"left":0.025930852,"top":0.1660016,"width":0.030917553,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.16679968,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.02825798,"top":0.16679968,"width":0.028590426,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.18355946,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"auth","depth":27,"bounds":{"left":0.025930852,"top":0.18355946,"width":0.008976064,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.18435754,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":3,"bounds":{"left":0.02825798,"top":0.18435754,"width":0.0066489363,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.20111732,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"certs","depth":27,"bounds":{"left":0.028590426,"top":0.20111732,"width":0.010305851,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.2019154,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.030917553,"top":0.2019154,"width":0.007978723,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.21867518,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"db","depth":27,"bounds":{"left":0.028590426,"top":0.21867518,"width":0.005319149,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.21947326,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":1,"bounds":{"left":0.03125,"top":0.21947326,"width":0.0026595744,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.23623304,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"media","depth":27,"bounds":{"left":0.028590426,"top":0.23623304,"width":0.012300532,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.23703113,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.032247342,"top":0.23703113,"width":0.008643617,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.25379092,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"templates","depth":27,"bounds":{"left":0.028590426,"top":0.25379092,"width":0.019946808,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.254589,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.03025266,"top":0.254589,"width":0.018284574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.2697526,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"bounds":{"left":0.028590426,"top":0.27134877,"width":0.00831117,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.27214685,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":3,"bounds":{"left":0.029920213,"top":0.27214685,"width":0.006981383,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.28731045,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"bounds":{"left":0.028590426,"top":0.28890663,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.2897047,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.029920213,"top":0.2897047,"width":0.024933511,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.3048683,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"docker-compose.yml","depth":27,"bounds":{"left":0.028590426,"top":0.3064645,"width":0.042220745,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.30726257,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":17,"bounds":{"left":0.03125,"top":0.30726257,"width":0.03956117,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.32242617,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"README.md","depth":27,"bounds":{"left":0.028590426,"top":0.32402235,"width":0.025265958,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.3415802,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"beszel","depth":27,"bounds":{"left":0.025930852,"top":0.3415802,"width":0.012965426,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.3423783,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.3423783,"width":0.010638298,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.35913807,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bitwarden","depth":27,"bounds":{"left":0.025930852,"top":0.35913807,"width":0.019946808,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.35993615,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.028590426,"top":0.35993615,"width":0.017287234,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.37669593,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dawarich","depth":27,"bounds":{"left":0.025930852,"top":0.37669593,"width":0.017952127,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.377494,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":7,"bounds":{"left":0.028590426,"top":0.377494,"width":0.015625,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.3942538,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dsk-uploader","depth":27,"bounds":{"left":0.025930852,"top":0.3942538,"width":0.026928192,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.39505187,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.028590426,"top":0.39505187,"width":0.024268618,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.39505187,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.41181165,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"finance","depth":27,"bounds":{"left":0.025930852,"top":0.41181165,"width":0.01462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.41260973,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.027593086,"top":0.41260973,"width":0.013297873,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.4293695,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"payments-logger","depth":27,"bounds":{"left":0.028590426,"top":0.4293695,"width":0.034574468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.4301676,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":14,"bounds":{"left":0.03125,"top":0.4301676,"width":0.031914894,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.44692737,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"flask-app","depth":27,"bounds":{"left":0.025930852,"top":0.44692737,"width":0.018949468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.44772545,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.027593086,"top":0.44772545,"width":0.017287234,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.44772545,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.46448523,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"garmin-connector","depth":27,"bounds":{"left":0.025930852,"top":0.46448523,"width":0.036236703,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.46528333,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":15,"bounds":{"left":0.028590426,"top":0.46528333,"width":0.033909574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.4820431,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"gitea","depth":27,"bounds":{"left":0.025930852,"top":0.4820431,"width":0.009973404,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.4828412,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.028590426,"top":0.4828412,"width":0.00731383,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.49960095,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health","depth":27,"bounds":{"left":0.025930852,"top":0.49960095,"width":0.012300532,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.50039905,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.50039905,"width":0.009973404,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.5171588,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health-tracker","depth":27,"bounds":{"left":0.025930852,"top":0.5171588,"width":0.028590426,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.5179569,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.028590426,"top":0.5179569,"width":0.025930852,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.53471667,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"homarr","depth":27,"bounds":{"left":0.025930852,"top":0.53471667,"width":0.014295213,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.5355148,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.5355148,"width":0.011968086,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.5522745,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"hst","depth":27,"bounds":{"left":0.025930852,"top":0.5522745,"width":0.0063164895,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.55307263,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":2,"bounds":{"left":0.028590426,"top":0.55307263,"width":0.003656915,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.5698324,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"immich","depth":27,"bounds":{"left":0.025930852,"top":0.5698324,"width":0.01462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.5706305,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.026928192,"top":0.5706305,"width":0.013630319,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.58739024,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jellyfinht","depth":27,"bounds":{"left":0.025930852,"top":0.58739024,"width":0.016954787,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.58818835,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.026928192,"top":0.58818835,"width":0.016289894,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.6049481,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"kavita","depth":27,"bounds":{"left":0.025930852,"top":0.6049481,"width":0.011635638,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.6057462,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.02825798,"top":0.6057462,"width":0.009640957,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.62250596,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"libreoffice","depth":27,"bounds":{"left":0.025930852,"top":0.62250596,"width":0.020279255,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.62330407,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.026928192,"top":0.62330407,"width":0.019281914,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.6400638,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"linkwarden","depth":27,"bounds":{"left":0.025930852,"top":0.6400638,"width":0.021609042,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.6408619,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.026928192,"top":0.6408619,"width":0.020611702,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.6576217,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":27,"bounds":{"left":0.025930852,"top":0.6576217,"width":0.030917553,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.6584198,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":14,"bounds":{"left":0.026928192,"top":0.6584198,"width":0.029920213,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.6584198,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.67517954,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic","depth":27,"bounds":{"left":0.028590426,"top":0.67517954,"width":0.015625,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.67597765,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.030917553,"top":0.67597765,"width":0.013297873,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.67597765,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.6927374,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"app","depth":27,"bounds":{"left":0.028590426,"top":0.6927374,"width":0.0076462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.6935355,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":2,"bounds":{"left":0.030917553,"top":0.6935355,"width":0.005319149,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.6935355,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.7102953,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mcp-server","depth":27,"bounds":{"left":0.028590426,"top":0.7102953,"width":0.023271276,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.71109337,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.032247342,"top":0.71109337,"width":0.019946808,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.72625697,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"bounds":{"left":0.028590426,"top":0.7278532,"width":0.00831117,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.7286512,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":3,"bounds":{"left":0.029920213,"top":0.7286512,"width":0.006981383,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.7438148,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"bounds":{"left":0.028590426,"top":0.74541104,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.7462091,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.029920213,"top":0.7462091,"width":0.024933511,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.7613727,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".gitignore","depth":27,"bounds":{"left":0.028590426,"top":0.7629689,"width":0.018949468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.76376694,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.029920213,"top":0.76376694,"width":0.017952127,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.77893054,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".mcp.json","depth":27,"bounds":{"left":0.028590426,"top":0.78052676,"width":0.019614361,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.7813248,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.029920213,"top":0.7813248,"width":0.018284574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.7813248,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.7964884,"width":0.0076462766,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic.ini","depth":27,"bounds":{"left":0.028590426,"top":0.7980846,"width":0.021609042,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.79888266,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.030917553,"top":0.79888266,"width":0.019281914,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.81404626,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"docker-compose.yml","depth":27,"bounds":{"left":0.028590426,"top":0.8156425,"width":0.042220745,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.8164405,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":17,"bounds":{"left":0.03125,"top":0.8164405,"width":0.03956117,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.8164405,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.8316041,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Dockerfile","depth":27,"bounds":{"left":0.028590426,"top":0.83320034,"width":0.020611702,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.8339984,"width":0.0033244682,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.031914894,"top":0.8339984,"width":0.017287234,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.849162,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"README.md","depth":27,"bounds":{"left":0.028590426,"top":0.8507582,"width":0.025265958,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.85155624,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.8667199,"width":0.0076462766,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"requirements.txt","depth":27,"bounds":{"left":0.028590426,"top":0.86831605,"width":0.032912236,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.8691141,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":15,"bounds":{"left":0.03025266,"top":0.8691141,"width":0.03158245,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.8691141,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.88427776,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"today_map.html","depth":27,"bounds":{"left":0.028590426,"top":0.8858739,"width":0.032247342,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.9034318,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mariadb","depth":27,"bounds":{"left":0.025930852,"top":0.9034318,"width":0.016289894,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.92098963,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"meeting-detector","depth":27,"bounds":{"left":0.025930852,"top":0.92098963,"width":0.03557181,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.9385475,"width":0.005319149,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mindfulmama","depth":27,"bounds":{"left":0.025930852,"top":0.9385475,"width":0.027260639,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Outline Section","depth":21,"bounds":{"left":0.015957447,"top":0.9473264,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.9497207,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"OUTLINE","depth":22,"bounds":{"left":0.022606382,"top":0.9473264,"width":0.01662234,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"OUTLINE","depth":23,"bounds":{"left":0.022606382,"top":0.95131683,"width":0.01662234,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Timeline Section","depth":21,"bounds":{"left":0.015957447,"top":0.9648843,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.96727854,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"TIMELINE","depth":22,"bounds":{"left":0.022606382,"top":0.9648843,"width":0.01761968,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"TIMELINE","depth":23,"bounds":{"left":0.022606382,"top":0.9688747,"width":0.01761968,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"bounds":{"left":0.11569149,"top":0.047885075,"width":0.09773936,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"bounds":{"left":0.21343085,"top":0.047885075,"width":0.09607713,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, Editor Group 1","depth":28,"bounds":{"left":0.30950797,"top":0.047885075,"width":0.07280585,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.12965426,"top":0.07821229,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","depth":28,"bounds":{"left":0.13763298,"top":0.105347164,"width":0.23803191,"height":0.014365523},"on_screen":true,"value":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"services:\n\n postgresql:\n image: docker.io/library/postgres:16-alpine\n container_name: Authentik-DB\n hostname: authentik-db\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}\"]\n interval: 5s\n timeout: 5s\n retries: 5\n environment:\n POSTGRES_PASSWORD: ${PG_PASS}\n POSTGRES_USER: authentik\n POSTGRES_DB: authentik\n volumes:\n - /volume2/docker/auth/db:/var/lib/postgresql/data\n networks:\n - authentik_internal\n\n redis:\n image: docker.io/library/redis:alpine\n container_name: Authentik-REDIS\n hostname: authentik-redis\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 5s\n timeout: 3s\n retries: 5\n networks:\n - authentik_internal\n\n server:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Server\n hostname: authentik-server\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\"\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}\n AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}\n ports:\n - \"9100:9000\"\n volumes:\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n - proxy\n\n worker:\n image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}\n container_name: Authentik-Worker\n hostname: authentik-worker\n restart: unless-stopped\n command: worker\n user: root\n environment:\n AUTHENTIK_REDIS__HOST: authentik-redis\n AUTHENTIK_POSTGRESQL__HOST: authentik-db\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - /volume2/docker/auth/media:/media\n - /volume2/docker/auth/certs:/certs\n - /volume2/docker/auth/templates:/templates\n depends_on:\n postgresql:\n condition: service_healthy\n redis:\n condition: service_healthy\n networks:\n - authentik_internal\n\n outpost:\n image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}\n container_name: Authentik-Outpost\n hostname: authentik-outpost\n restart: unless-stopped\n environment:\n AUTHENTIK_HOST: ${AUTHENTIK_HOST}\n AUTHENTIK_INSECURE: \"false\"\n AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}\n ports:\n - \"9101:9000\"\n depends_on:\n - server\n networks:\n - authentik_internal\n - proxy\n\nnetworks:\n authentik_internal:\n internal: true\n proxy:\n external: true","depth":29,"bounds":{"left":0.13763298,"top":0.10694334,"width":0.23803191,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Review payment logger au…, Editor Group 2","depth":28,"bounds":{"left":0.5578458,"top":0.047885075,"width":0.07679521,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"bounds":{"left":0.63464093,"top":0.047885075,"width":0.0631649,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.57214093,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"on_screen":false,"role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Problems (⇧⌘M)","depth":22,"bounds":{"left":0.118351065,"top":0.7278532,"width":0.027925532,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"PROBLEMS","depth":24,"bounds":{"left":0.122340426,"top":0.7366321,"width":0.019946808,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Output (⇧⌘U)","depth":22,"bounds":{"left":0.14594415,"top":0.7278532,"width":0.023603724,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUTPUT","depth":24,"bounds":{"left":0.14993352,"top":0.7366321,"width":0.015625,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Debug Console (⇧⌘Y)","depth":22,"bounds":{"left":0.16921543,"top":0.7278532,"width":0.039893616,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DEBUG CONSOLE","depth":24,"bounds":{"left":0.1732048,"top":0.7366321,"width":0.031914894,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Terminal (⌃`)","depth":22,"bounds":{"left":0.2087766,"top":0.7278532,"width":0.026595745,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"TERMINAL","depth":24,"bounds":{"left":0.21276596,"top":0.7366321,"width":0.01861702,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Ports","depth":22,"bounds":{"left":0.23537233,"top":0.7278532,"width":0.020279255,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"PORTS","depth":24,"bounds":{"left":0.2393617,"top":0.7366321,"width":0.012300532,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"remote SSH: nas","depth":16,"bounds":{"left":0.0006648936,"top":0.98244214,"width":0.028590426,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.0033244682,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SSH: nas","depth":17,"bounds":{"left":0.008643617,"top":0.9856345,"width":0.017952127,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git)","depth":16,"bounds":{"left":0.030917553,"top":0.98244214,"width":0.03756649,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.031914894,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":17,"bounds":{"left":0.03723404,"top":0.9856345,"width":0.03025266,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - main*, Checkout Branch/Tag...","depth":16,"bounds":{"left":0.06815159,"top":0.98244214,"width":0.019614361,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.069148935,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"main*","depth":17,"bounds":{"left":0.07446808,"top":0.9856345,"width":0.012300532,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - Synchronize Changes","depth":16,"bounds":{"left":0.08743351,"top":0.98244214,"width":0.0076462766,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"No Problems","depth":16,"bounds":{"left":0.09740692,"top":0.98244214,"width":0.022606382,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.09906915,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.1043883,"top":0.9856345,"width":0.004986702,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.109042555,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.1143617,"top":0.9856345,"width":0.0039893617,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"No Ports Forwarded","depth":16,"bounds":{"left":0.12167553,"top":0.98244214,"width":0.012632979,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.12333777,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.12865691,"top":0.9856345,"width":0.0039893617,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Notifications","depth":16,"bounds":{"left":0.9886968,"top":0.98244214,"width":0.010638298,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sign In","depth":16,"bounds":{"left":0.9650931,"top":0.98244214,"width":0.022606382,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.96675533,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sign In","depth":17,"bounds":{"left":0.97207445,"top":0.9856345,"width":0.013962766,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Compose","depth":16,"bounds":{"left":0.9424867,"top":0.98244214,"width":0.020279255,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions","depth":16,"bounds":{"left":0.93517286,"top":0.98244214,"width":0.0076462766,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"LF","depth":16,"bounds":{"left":0.92486703,"top":0.98244214,"width":0.007978723,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"UTF-8","depth":16,"bounds":{"left":0.90724736,"top":0.98244214,"width":0.015625,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Spaces: 2","depth":16,"bounds":{"left":0.88331115,"top":0.98244214,"width":0.022273935,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ln 1, Col 1","depth":16,"bounds":{"left":0.85771275,"top":0.98244214,"width":0.023936171,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"collapsed","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command Succeeded","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":16,"bounds":{"left":0.8507314,"top":0.95211494,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination.","depth":16,"bounds":{"left":0.8580452,"top":0.952913,"width":0.13763298,"height":0.011971269},"on_screen":true,"role_description":"text"}]...
|
8946020387746705504
|
8416343648516981816
|
click
|
accessibility
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
certs
db
media
templates
.env
.env.example
docker-compose.yml
README.md
beszel
bitwarden
dawarich
dsk-uploader
finance
payments-logger
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
docker-compose.yml, Editor Group 1
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: Authentik-DB
hostname: authentik-db
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-authentik} -U ${POSTGRES_USER:-authentik}"]
interval: 5s
timeout: 5s
retries: 5
environment:
POSTGRES_PASSWORD: [PASSWORD]
POSTGRES_USER: authentik
POSTGRES_DB: authentik
volumes:
- /volume2/docker/auth/db:/var/lib/postgresql/data
networks:
- authentik_internal
redis:
image: docker.io/library/redis:alpine
container_name: Authentik-REDIS
hostname: authentik-redis
restart: unless-stopped
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 5
networks:
- authentik_internal
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Server
hostname: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: [PASSWORD]
ports:
- "9100:9000"
volumes:
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
- proxy
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
container_name: Authentik-Worker
hostname: authentik-worker
restart: unless-stopped
command: worker
user: root
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: [PASSWORD]
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /volume2/docker/auth/media:/media
- /volume2/docker/auth/certs:/certs
- /volume2/docker/auth/templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- authentik_internal
outpost:
image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG}
container_name: Authentik-Outpost
hostname: authentik-outpost
restart: unless-stopped
environment:
AUTHENTIK_HOST: ${AUTHENTIK_HOST}
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
ports:
- "9101:9000"
depends_on:
- server
networks:
- authentik_internal
- proxy
networks:
authentik_internal:
internal: true
proxy:
external: true
Review payment logger au…, Editor Group 2
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports
PORTS
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
collapsed
Command Succeeded
The file(s) to paste have been deleted or moved since you copied them. Unable to move/copy 'vscode-remote://ssh-remote+nas/volume2/docker/auth' because target 'vscode-remote://ssh-remote+nas/volume2/docker/finance/auth' already exists at destination....
|
NULL
|
NULL
|
NULL
|
NULL
|
|
14374
|
639
|
5
|
2026-05-09T17:28:09.467585+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-09/1778 /Users/lukas/.screenpipe/data/data/2026-05-09/1778347689467_m1.jpg...
|
Firefox
|
lakylak/finance-hub - finance-hub - Gitea: Git wit lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea — Personal...
|
True
|
gitea.com/lakylak/finance-hub
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Clo Pull requests · screenpipe/screenpipe · GitHub
Close tab
DNS / Nameservers | Hostinger
Close tab
Nginx Proxy Manager
Close tab
Screenpipe — Archive
Close tab
SQLite Web: archive.db
Close tab
SQLite Web: db.sqlite
Close tab
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
Close tab
DXP4800PLUS-B5F8
Close tab
AFFiNE - All In One KnowledgeOS
Close tab
All docs · AFFiNE
Close tab
Payments Logger
Close tab
Your old PC can run Windows 11 in a VM, but not on bare metal - [EMAIL] - Gmail
Close tab
Location Logger
Close tab
Finance Hub
Close tab
Finance Hub
Close tab
Select: transactions - db - Adminer
Close tab
Claude Code | Claude Platform
Close tab
April 2026 spending by category - Claude
Close tab
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Close tab
Applications - Admin - authentik
Close tab
New Tab
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Customize sidebar
Dashboard
Issues
Issues
Pull Requests
Pull Requests
Milestones
Milestones
Explore
Explore
Notifications
lakylak
lakylak
/
finance-hub
finance-hub
Private
RSS Feed
Unwatch
1
1
Star
0
0
Fork
Fork
0
0
Code
Code
Issues
Issues
Pull Requests
Pull Requests
Actions
Actions
Projects
Projects
Releases
Releases
Wiki
Wiki
Activity
Activity
Settings
Settings
3 Commits
3
Commits
1 Branch
1
Branch
0 Tags
0
Tags
main
New Pull Request
Go to file
Code
Code
Lukas Kovalik
640e0d609d
640e0d609d
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
...
5 minutes ago
backend
backend
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
frontend
frontend
Initial commit: finance-hub unified finance app
Initial commit: finance-hub unified finance app
1 hour ago
mcp
mcp
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
scripts
scripts
Initial commit: finance-hub unified finance app
Initial commit: finance-hub unified finance app
1 hour ago
.env.example
.env.example
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
.gitignore
.gitignore
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
docker-compose.yml
docker-compose.yml
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
README.md
README.md
Add README with project description, API reference, and usage guide
Add README with project description, API reference, and usage guide
1 hour ago
README.md Escape Edit File
README.md
README.md
Escape
Edit File
Finance Hub
Finance Hub
Unified personal finance tracker that consolidates DSK Bank SMS notifications and CSV statement exports into a single PostgreSQL database with a React review UI.
Replaces two separate tools:
payments-logger
— ingested DSK Bank SMS notifications
dsk-uploader
— parsed DSK Bank CSV exports and sent them to Notion
Features
Features
SMS ingest
— receives DSK Bank payment SMS via iOS Shortcuts / HTTP POST; parses amount, recipient, card, date, balance
CSV upload
— drag-and-drop DSK Bank account statement exports (BGN and EUR formats, cp1251 and UTF-8 encodings)
Deduplication
— exact
rawMessage
match prevents duplicate imports from re-uploads or re-runs of the reimport script
Auto-tagging
— new imports for a known recipient are automatically tagged based on tag history for that payee
Rule-based tagging
— CSV rows are auto-tagged by keyword rules (LIDL → Groceries, NETFLIX → Subscriptions, etc.)
Deduplication display
— same-day same-amount records from SMS and CSV are shown as a single "SMS + CSV" row in the UI
Notification forwarding
— sends payment summaries to a configurable notifier service (Viber, etc.)
Tag management
— create, colour-code, and remove tags on individual transactions
Settings
— column visibility, source row colouring, table density, mobile layout, hide balance-alert SMS
Auth
— delegated to Authentik via NPM reverse proxy header injection; no local user accounts
Tech Stack
Tech Stack
Layer
Technology
Backend
Node.js 20, Express, Prisma 5
Database
PostgreSQL 16
CSV parsing
csv-parse
,
iconv-lite
(cp1251 + UTF-8 BOM)
File upload
multer
(memory storage)
Frontend
React 18, Vite, Tailwind CSS, Lucide React
Auth
Authentik forward-auth via
X-authentik-username
header
Container
Docker Compose
Layer
Backend
Database
CSV parsing
File upload
Frontend
Auth
Container
Technology
Node.js 20, Express, Prisma 5
PostgreSQL 16
csv-parse
,
iconv-lite
(cp1251 + UTF-8 BOM)
multer
(memory storage)
React 18, Vite, Tailwind CSS, Lucide React
Authentik forward-auth via
X-authentik-username
header
Docker Compose
Quick Start
Quick Start
1. Clone and configure
1. Clone and configure
git clone [EMAIL]:lakylak/finance-hub.git
cd
finance-hub
cp .env.example .env
# Edit .env — set DB_PASSWORD and notifier settings
2. Start
2. Start
docker compose up -d --build
Services:
Service
Default port
Frontend
5175
Backend API
3001
Adminer (DB UI)
8092
Service
Frontend
Backend API
Adminer (DB UI)
Default port
5175
3001
8092
3. First run
3. First run
Prisma migrations run automatically on backend startup via
prisma migrate deploy
.
Environment variables
Environment variables
Variable
Required
Description
DB_PASSWORD
Yes
PostgreSQL password
NOTIFIER_URL
No
Base URL of the notifier service
NOTIFIER_CHANNEL
No
Channel to use (
viber
, etc.) Default:
viber
NOTIFY_DEFAULT_PHONE
No
Phone number for payment notifications
TZ
No
Timezone for SMS date parsing. Default:
Europe/Sofia
BACKEND_PORT
No
Backend listen port. Default:
3001
FRONTEND_PORT
No
Frontend listen port. Default:
5175
DEV_BYPASS_AUTH
No
Set
true
to skip Authentik header check during local dev
Variable
DB_PASSWORD
NOTIFIER_URL
NOTIFIER_CHANNEL
NOTIFY_DEFAULT_PHONE
TZ
BACKEND_PORT
FRONTEND_PORT
DEV_BYPASS_AUTH
Required
Yes
No
No
No
No
No
No
No
Description
PostgreSQL password
Base URL of the notifier service
Channel to use (
viber
, etc.) Default:
viber
Phone number for payment notifications
Timezone for SMS date parsing. Default:
Europe/Sofia
Backend listen port. Default:
3001
Frontend listen port. Default:
5175
Set
true
to skip Authentik header check during local dev
Authentication
Authentication
In production, route the frontend through
Nginx Proxy Manager
with an
Authentik forward-auth
provider. NPM injects
X-authentik-username
(and optionally
X-authentik-email
,
X-authentik-groups
) into every proxied request.
The backend reads these headers and rejects requests without them (unless
DEV_BYPASS_AUTH=true
).
The public endpoints below are exempt and require no authentication:
GET /api/health
POST /api/payments/ingest
Sign-out link:
/outpost.goauthentik.io/sign_out
API Reference
API Reference
Health
Health
GET /api/health
GET /api/health
Public. Returns service status and database connection info.
{
"status"
:
"ok"
,
"timestamp"
:
"2026-05-09T10:00:00.000Z"
,
"storage"
:
{
"type"
:
"PostgreSQL"
,
"host"
:
"db"
,
"database"
:
"finance_hub"
}
}
Payments (Transaction Imports)
Payments (Transaction Imports)
Base path:
/api/payments
All endpoints require authentication except
/ingest
.
POST /api/payments/ingest
POST /api/payments/ingest
Public.
Ingest a DSK Bank SMS notification or structured (Apple Wallet) payment.
Rate limited to
200 requests/minute
.
Returns
409
if the exact message was already imported.
SMS body:
{
"message"
:
"DSK Bank. Na 08/05/2026 v 19:32 sa plateni 67.81 EUR s karta 400915***4447 na POS s adres: LIDL BALGARIYA EOOD, SOFIYA, BGR. Nalichni: 2011.57 EUR."
}
Structured (Apple Wallet) body:
{
"ingestMode"
:
"apple_wallet"
,
"amount"
:
12.50
,
"recipient"
:
"Starbucks"
,
"type"
:
"WALLET"
,
"card"
:
"4444"
,
"date"
:
"2026-05-09T10:30:00Z"
,
"balance"
:
null
}
Response:
201 Created
— the created import record.
Duplicate:
409 Conflict
—
{ "error": "Already imported", "existing": { ... } }
GET /api/payments
GET /api/payments
List transaction imports with filtering, sorting, and pagination.
Query parameters:
Parameter
Type
Description
page
int
Page number. Default:
1
limit
int
Records per page, max 200. Default:
50
sortBy
string
Field to sort by:
date
,
amount
,
balance
,
recipient
,
type
,
source
,
createdAt
,
status
. Default:
createdAt
sortDir
string
asc
or
desc
. Default:
desc
status
string
Filter by status:
UNPROCESSED
,
SENT
,
SKIPPED
source
string
Filter by source:
INGEST
,
UPLOAD
type
string
Filter by transaction type (e.g.
POS
,
ATM
)
tag
string
Filter by tag name
recipient
string
Case-insensitive substring match on recipient
search
string
Case-insensitive search across
rawMessage
and
recipient
dateFrom
date
ISO date string — inclusive start of date range
dateTo
date
ISO date string — inclusive end of date range (interpreted as end of day)
hideBalanceAlerts
boolean
true
to exclude balance-notification SMS and records with no parsed amount
Parameter
page
limit
sortBy
sortDir
status
source
type
tag
recipient
search
dateFrom
dateTo
hideBalanceAlerts
Type
int
int
string
string
string
string
string
string
string
string
date
date
boolean
Description
Page number. Default:
1
Records per page, max 200. Default:
50
Field to sort by:
date
,
amount
,
balance
,
recipient
,
type
,
source
,
createdAt
,
status
. Default:
createdAt
asc
or
desc
. Default:...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"DNS / Nameservers | Hostinger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Nginx Proxy Manager","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"AFFiNE - All In One KnowledgeOS","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"All docs · AFFiNE","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Payments Logger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Your old PC can run Windows 11 in a VM, but not on bare metal - kovaliklukas@gmail.com - Gmail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Location Logger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Select: transactions - db - Adminer","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Claude Code | Claude Platform","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"April 2026 spending by category - Claude","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Applications - Admin - authentik","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Dashboard","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Issues","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull Requests","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull Requests","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Milestones","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Milestones","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Explore","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Explore","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Notifications","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"lakylak","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"lakylak","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"finance-hub","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"finance-hub","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Private","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"RSS Feed","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Unwatch","depth":7,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"1","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Star","depth":7,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"0","depth":7,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Fork","depth":8,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Fork","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"0","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Issues","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull Requests","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull Requests","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Projects","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Projects","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Releases","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Releases","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Activity","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Activity","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"3 Commits","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Commits","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"1 Branch","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"1","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Branch","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"0 Tags","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tags","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"main","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Pull Request","depth":7,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Go to file","depth":9,"on_screen":false,"help_text":"","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":7,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"640e0d609d","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"640e0d609d","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"...","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"5 minutes ago","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"backend","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"backend","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5 minutes ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"frontend","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"frontend","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Initial commit: finance-hub unified finance app","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Initial commit: finance-hub unified finance app","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1 hour ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"mcp","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"mcp","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5 minutes ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"scripts","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"scripts","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Initial commit: finance-hub unified finance app","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Initial commit: finance-hub unified finance app","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1 hour ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":".env.example","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":".env.example","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5 minutes ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":".gitignore","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":".gitignore","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5 minutes ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"docker-compose.yml","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"docker-compose.yml","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add Authentik auth, API key support, and remote MCP server","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add Authentik auth, API key support, and remote MCP server","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5 minutes ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"README.md","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"README.md","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Add README with project description, API reference, and usage guide","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Add README with project description, API reference, and usage guide","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1 hour ago","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"README.md Escape Edit File","depth":8,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"README.md","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"README.md","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Escape","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Edit File","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Finance Hub","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Finance Hub","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unified personal finance tracker that consolidates DSK Bank SMS notifications and CSV statement exports into a single PostgreSQL database with a React review UI.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Replaces two separate tools:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"payments-logger","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— ingested DSK Bank SMS notifications","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dsk-uploader","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— parsed DSK Bank CSV exports and sent them to Notion","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Features","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Features","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SMS ingest","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— receives DSK Bank payment SMS via iOS Shortcuts / HTTP POST; parses amount, recipient, card, date, balance","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CSV upload","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— drag-and-drop DSK Bank account statement exports (BGN and EUR formats, cp1251 and UTF-8 encodings)","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deduplication","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— exact","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rawMessage","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"match prevents duplicate imports from re-uploads or re-runs of the reimport script","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auto-tagging","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— new imports for a known recipient are automatically tagged based on tag history for that payee","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Rule-based tagging","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— CSV rows are auto-tagged by keyword rules (LIDL → Groceries, NETFLIX → Subscriptions, etc.)","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Deduplication display","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— same-day same-amount records from SMS and CSV are shown as a single \"SMS + CSV\" row in the UI","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Notification forwarding","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— sends payment summaries to a configurable notifier service (Viber, etc.)","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tag management","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— create, colour-code, and remove tags on individual transactions","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— column visibility, source row colouring, table density, mobile layout, hide balance-alert SMS","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auth","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— delegated to Authentik via NPM reverse proxy header injection; no local user accounts","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Tech Stack","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Tech Stack","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Layer","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Technology","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Node.js 20, Express, Prisma 5","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Database","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PostgreSQL 16","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CSV parsing","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"csv-parse","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iconv-lite","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(cp1251 + UTF-8 BOM)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File upload","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"multer","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(memory storage)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"React 18, Vite, Tailwind CSS, Lucide React","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auth","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Authentik forward-auth via","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"X-authentik-username","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"header","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Container","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docker Compose","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Layer","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Database","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"CSV parsing","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"File upload","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Auth","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Container","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Technology","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Node.js 20, Express, Prisma 5","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PostgreSQL 16","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"csv-parse","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"iconv-lite","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(cp1251 + UTF-8 BOM)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"multer","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(memory storage)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"React 18, Vite, Tailwind CSS, Lucide React","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Authentik forward-auth via","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"X-authentik-username","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"header","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Docker Compose","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Quick Start","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Quick Start","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"1. Clone and configure","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. Clone and configure","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"git clone git@gitea.com:lakylak/finance-hub.git","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"cd","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"finance-hub\ncp .env.example .env","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"# Edit .env — set DB_PASSWORD and notifier settings","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"2. Start","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. Start","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"docker compose up -d --build","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Services:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Service","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Default port","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5175","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend API","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3001","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adminer (DB UI)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8092","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Service","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend API","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adminer (DB UI)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Default port","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5175","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3001","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8092","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"3. First run","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. First run","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Prisma migrations run automatically on backend startup via","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"prisma migrate deploy","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Environment variables","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Environment variables","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Variable","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Required","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Description","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DB_PASSWORD","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Yes","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PostgreSQL password","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFIER_URL","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Base URL of the notifier service","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFIER_CHANNEL","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Channel to use (","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viber","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", etc.) Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viber","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFY_DEFAULT_PHONE","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Phone number for payment notifications","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TZ","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Timezone for SMS date parsing. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Europe/Sofia","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BACKEND_PORT","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend listen port. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3001","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FRONTEND_PORT","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend listen port. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5175","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEV_BYPASS_AUTH","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Set","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to skip Authentik header check during local dev","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Variable","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DB_PASSWORD","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFIER_URL","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFIER_CHANNEL","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"NOTIFY_DEFAULT_PHONE","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"TZ","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"BACKEND_PORT","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"FRONTEND_PORT","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEV_BYPASS_AUTH","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Required","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Yes","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Description","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"PostgreSQL password","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Base URL of the notifier service","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Channel to use (","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viber","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", etc.) Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"viber","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Phone number for payment notifications","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Timezone for SMS date parsing. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Europe/Sofia","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Backend listen port. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3001","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Frontend listen port. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5175","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Set","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to skip Authentik header check during local dev","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Authentication","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Authentication","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In production, route the frontend through","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Nginx Proxy Manager","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with an","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Authentik forward-auth","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"provider. NPM injects","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"X-authentik-username","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(and optionally","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"X-authentik-email","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"X-authentik-groups","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") into every proxied request.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The backend reads these headers and rejects requests without them (unless","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"DEV_BYPASS_AUTH=true","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":").","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The public endpoints below are exempt and require no authentication:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET /api/health","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST /api/payments/ingest","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sign-out link:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/outpost.goauthentik.io/sign_out","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"API Reference","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"API Reference","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Health","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Health","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"GET /api/health","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET /api/health","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Public. Returns service status and database connection info.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"status\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"ok\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"timestamp\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"2026-05-09T10:00:00.000Z\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"storage\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"type\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"PostgreSQL\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"host\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"db\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"database\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"finance_hub\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Payments (Transaction Imports)","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Payments (Transaction Imports)","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Base path:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/api/payments","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"All endpoints require authentication except","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/ingest","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"POST /api/payments/ingest","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POST /api/payments/ingest","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Public.","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ingest a DSK Bank SMS notification or structured (Apple Wallet) payment.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Rate limited to","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"200 requests/minute","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Returns","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"409","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"if the exact message was already imported.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SMS body:","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"message\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"DSK Bank. Na 08/05/2026 v 19:32 sa plateni 67.81 EUR s karta 400915***4447 na POS s adres: LIDL BALGARIYA EOOD, SOFIYA, BGR. Nalichni: 2011.57 EUR.\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Structured (Apple Wallet) body:","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"ingestMode\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"apple_wallet\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"amount\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12.50","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"recipient\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"Starbucks\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"type\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"WALLET\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"card\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"4444\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"date\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"2026-05-09T10:30:00Z\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\"balance\"","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"null","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"}","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Response:","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"201 Created","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"— the created import record.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Duplicate:","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"409 Conflict","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"{ \"error\": \"Already imported\", \"existing\": { ... } }","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"GET /api/payments","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GET /api/payments","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"List transaction imports with filtering, sorting, and pagination.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Query parameters:","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Parameter","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Description","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"page","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"int","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Page number. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"limit","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"int","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Records per page, max 200. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sortBy","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Field to sort by:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"amount","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"balance","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recipient","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"type","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"source","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createdAt","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"status","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createdAt","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sortDir","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"asc","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"desc","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"desc","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"status","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filter by status:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UNPROCESSED","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SENT","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SKIPPED","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"source","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filter by source:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"INGEST","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"UPLOAD","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"type","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filter by transaction type (e.g.","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"POS","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ATM","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"tag","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Filter by tag name","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recipient","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Case-insensitive substring match on recipient","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"search","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Case-insensitive search across","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"rawMessage","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recipient","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dateFrom","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ISO date string — inclusive start of date range","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dateTo","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ISO date string — inclusive end of date range (interpreted as end of day)","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"hideBalanceAlerts","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"boolean","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to exclude balance-notification SMS and records with no parsed amount","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Parameter","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"page","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"limit","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sortBy","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"sortDir","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"status","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"source","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"type","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"tag","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recipient","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"search","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dateFrom","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"dateTo","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"hideBalanceAlerts","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"int","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"int","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"string","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"boolean","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Description","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Page number. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Records per page, max 200. Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"50","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Field to sort by:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"date","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"amount","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"balance","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"recipient","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"type","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"source","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createdAt","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"status","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"createdAt","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"asc","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"desc","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". Default:","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8944209490759415171
|
-430422418760525498
|
idle
|
accessibility
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Clo Pull requests · screenpipe/screenpipe · GitHub
Close tab
DNS / Nameservers | Hostinger
Close tab
Nginx Proxy Manager
Close tab
Screenpipe — Archive
Close tab
SQLite Web: archive.db
Close tab
SQLite Web: db.sqlite
Close tab
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
Close tab
DXP4800PLUS-B5F8
Close tab
AFFiNE - All In One KnowledgeOS
Close tab
All docs · AFFiNE
Close tab
Payments Logger
Close tab
Your old PC can run Windows 11 in a VM, but not on bare metal - [EMAIL] - Gmail
Close tab
Location Logger
Close tab
Finance Hub
Close tab
Finance Hub
Close tab
Select: transactions - db - Adminer
Close tab
Claude Code | Claude Platform
Close tab
April 2026 spending by category - Claude
Close tab
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Close tab
Applications - Admin - authentik
Close tab
New Tab
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Customize sidebar
Dashboard
Issues
Issues
Pull Requests
Pull Requests
Milestones
Milestones
Explore
Explore
Notifications
lakylak
lakylak
/
finance-hub
finance-hub
Private
RSS Feed
Unwatch
1
1
Star
0
0
Fork
Fork
0
0
Code
Code
Issues
Issues
Pull Requests
Pull Requests
Actions
Actions
Projects
Projects
Releases
Releases
Wiki
Wiki
Activity
Activity
Settings
Settings
3 Commits
3
Commits
1 Branch
1
Branch
0 Tags
0
Tags
main
New Pull Request
Go to file
Code
Code
Lukas Kovalik
640e0d609d
640e0d609d
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
...
5 minutes ago
backend
backend
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
frontend
frontend
Initial commit: finance-hub unified finance app
Initial commit: finance-hub unified finance app
1 hour ago
mcp
mcp
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
scripts
scripts
Initial commit: finance-hub unified finance app
Initial commit: finance-hub unified finance app
1 hour ago
.env.example
.env.example
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
.gitignore
.gitignore
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
docker-compose.yml
docker-compose.yml
Add Authentik auth, API key support, and remote MCP server
Add Authentik auth, API key support, and remote MCP server
5 minutes ago
README.md
README.md
Add README with project description, API reference, and usage guide
Add README with project description, API reference, and usage guide
1 hour ago
README.md Escape Edit File
README.md
README.md
Escape
Edit File
Finance Hub
Finance Hub
Unified personal finance tracker that consolidates DSK Bank SMS notifications and CSV statement exports into a single PostgreSQL database with a React review UI.
Replaces two separate tools:
payments-logger
— ingested DSK Bank SMS notifications
dsk-uploader
— parsed DSK Bank CSV exports and sent them to Notion
Features
Features
SMS ingest
— receives DSK Bank payment SMS via iOS Shortcuts / HTTP POST; parses amount, recipient, card, date, balance
CSV upload
— drag-and-drop DSK Bank account statement exports (BGN and EUR formats, cp1251 and UTF-8 encodings)
Deduplication
— exact
rawMessage
match prevents duplicate imports from re-uploads or re-runs of the reimport script
Auto-tagging
— new imports for a known recipient are automatically tagged based on tag history for that payee
Rule-based tagging
— CSV rows are auto-tagged by keyword rules (LIDL → Groceries, NETFLIX → Subscriptions, etc.)
Deduplication display
— same-day same-amount records from SMS and CSV are shown as a single "SMS + CSV" row in the UI
Notification forwarding
— sends payment summaries to a configurable notifier service (Viber, etc.)
Tag management
— create, colour-code, and remove tags on individual transactions
Settings
— column visibility, source row colouring, table density, mobile layout, hide balance-alert SMS
Auth
— delegated to Authentik via NPM reverse proxy header injection; no local user accounts
Tech Stack
Tech Stack
Layer
Technology
Backend
Node.js 20, Express, Prisma 5
Database
PostgreSQL 16
CSV parsing
csv-parse
,
iconv-lite
(cp1251 + UTF-8 BOM)
File upload
multer
(memory storage)
Frontend
React 18, Vite, Tailwind CSS, Lucide React
Auth
Authentik forward-auth via
X-authentik-username
header
Container
Docker Compose
Layer
Backend
Database
CSV parsing
File upload
Frontend
Auth
Container
Technology
Node.js 20, Express, Prisma 5
PostgreSQL 16
csv-parse
,
iconv-lite
(cp1251 + UTF-8 BOM)
multer
(memory storage)
React 18, Vite, Tailwind CSS, Lucide React
Authentik forward-auth via
X-authentik-username
header
Docker Compose
Quick Start
Quick Start
1. Clone and configure
1. Clone and configure
git clone [EMAIL]:lakylak/finance-hub.git
cd
finance-hub
cp .env.example .env
# Edit .env — set DB_PASSWORD and notifier settings
2. Start
2. Start
docker compose up -d --build
Services:
Service
Default port
Frontend
5175
Backend API
3001
Adminer (DB UI)
8092
Service
Frontend
Backend API
Adminer (DB UI)
Default port
5175
3001
8092
3. First run
3. First run
Prisma migrations run automatically on backend startup via
prisma migrate deploy
.
Environment variables
Environment variables
Variable
Required
Description
DB_PASSWORD
Yes
PostgreSQL password
NOTIFIER_URL
No
Base URL of the notifier service
NOTIFIER_CHANNEL
No
Channel to use (
viber
, etc.) Default:
viber
NOTIFY_DEFAULT_PHONE
No
Phone number for payment notifications
TZ
No
Timezone for SMS date parsing. Default:
Europe/Sofia
BACKEND_PORT
No
Backend listen port. Default:
3001
FRONTEND_PORT
No
Frontend listen port. Default:
5175
DEV_BYPASS_AUTH
No
Set
true
to skip Authentik header check during local dev
Variable
DB_PASSWORD
NOTIFIER_URL
NOTIFIER_CHANNEL
NOTIFY_DEFAULT_PHONE
TZ
BACKEND_PORT
FRONTEND_PORT
DEV_BYPASS_AUTH
Required
Yes
No
No
No
No
No
No
No
Description
PostgreSQL password
Base URL of the notifier service
Channel to use (
viber
, etc.) Default:
viber
Phone number for payment notifications
Timezone for SMS date parsing. Default:
Europe/Sofia
Backend listen port. Default:
3001
Frontend listen port. Default:
5175
Set
true
to skip Authentik header check during local dev
Authentication
Authentication
In production, route the frontend through
Nginx Proxy Manager
with an
Authentik forward-auth
provider. NPM injects
X-authentik-username
(and optionally
X-authentik-email
,
X-authentik-groups
) into every proxied request.
The backend reads these headers and rejects requests without them (unless
DEV_BYPASS_AUTH=true
).
The public endpoints below are exempt and require no authentication:
GET /api/health
POST /api/payments/ingest
Sign-out link:
/outpost.goauthentik.io/sign_out
API Reference
API Reference
Health
Health
GET /api/health
GET /api/health
Public. Returns service status and database connection info.
{
"status"
:
"ok"
,
"timestamp"
:
"2026-05-09T10:00:00.000Z"
,
"storage"
:
{
"type"
:
"PostgreSQL"
,
"host"
:
"db"
,
"database"
:
"finance_hub"
}
}
Payments (Transaction Imports)
Payments (Transaction Imports)
Base path:
/api/payments
All endpoints require authentication except
/ingest
.
POST /api/payments/ingest
POST /api/payments/ingest
Public.
Ingest a DSK Bank SMS notification or structured (Apple Wallet) payment.
Rate limited to
200 requests/minute
.
Returns
409
if the exact message was already imported.
SMS body:
{
"message"
:
"DSK Bank. Na 08/05/2026 v 19:32 sa plateni 67.81 EUR s karta 400915***4447 na POS s adres: LIDL BALGARIYA EOOD, SOFIYA, BGR. Nalichni: 2011.57 EUR."
}
Structured (Apple Wallet) body:
{
"ingestMode"
:
"apple_wallet"
,
"amount"
:
12.50
,
"recipient"
:
"Starbucks"
,
"type"
:
"WALLET"
,
"card"
:
"4444"
,
"date"
:
"2026-05-09T10:30:00Z"
,
"balance"
:
null
}
Response:
201 Created
— the created import record.
Duplicate:
409 Conflict
—
{ "error": "Already imported", "existing": { ... } }
GET /api/payments
GET /api/payments
List transaction imports with filtering, sorting, and pagination.
Query parameters:
Parameter
Type
Description
page
int
Page number. Default:
1
limit
int
Records per page, max 200. Default:
50
sortBy
string
Field to sort by:
date
,
amount
,
balance
,
recipient
,
type
,
source
,
createdAt
,
status
. Default:
createdAt
sortDir
string
asc
or
desc
. Default:
desc
status
string
Filter by status:
UNPROCESSED
,
SENT
,
SKIPPED
source
string
Filter by source:
INGEST
,
UPLOAD
type
string
Filter by transaction type (e.g.
POS
,
ATM
)
tag
string
Filter by tag name
recipient
string
Case-insensitive substring match on recipient
search
string
Case-insensitive search across
rawMessage
and
recipient
dateFrom
date
ISO date string — inclusive start of date range
dateTo
date
ISO date string — inclusive end of date range (interpreted as end of day)
hideBalanceAlerts
boolean
true
to exclude balance-notification SMS and records with no parsed amount
Parameter
page
limit
sortBy
sortDir
status
source
type
tag
recipient
search
dateFrom
dateTo
hideBalanceAlerts
Type
int
int
string
string
string
string
string
string
string
string
date
date
boolean
Description
Page number. Default:
1
Records per page, max 200. Default:
50
Field to sort by:
date
,
amount
,
balance
,
recipient
,
type
,
source
,
createdAt
,
status
. Default:
createdAt
asc
or
desc
. Default:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
24230
|
1011
|
26
|
2026-05-12T08:49:25.872062+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778575765872_m2.jpg...
|
Slack
|
Steliyan Georgiev (DM) - Jiminny Inc - 4 new items Steliyan Georgiev (DM) - Jiminny Inc - 4 new items - Slack...
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Steliyan Georgiev
Petko Kashinski
Galya Dimitrova
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Stoyan Tanev
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 10:40:03 AM
10:40 AM
здрасти Стели
Today at 10:40:36 AM
10:40
Галя ме помоли да видим това със липсващ pdf_url на репорти
Today at 10:41:14 AM
10:41
може ли по-късно да се чуем да видим какво може да направим
Steliyan Georgiev
Today at 10:44:58 AM
10:44 AM
Здрасти Лукаш, да
има ли тикет?
Lukas Kovalik
Today at 10:46:01 AM
10:46 AM
https://jiminny.atlassian.net/browse/JY-20776
https://jiminny.atlassian.net/browse/JY-20776
Jira Cloud
Jira Cloud
Remove preview
Jira Cloud Bug JY-20776 Automated report - sentry Bug JY-20776 in Jira Cloud Preview in Slack Status Backlog Priority Medium Medium Assignee Unassigned Unassigned As of today at 10:46 AM Refresh Open in Jira ✨ Summarise
Automated report - sentry
Bug JY-20776 in Jira Cloud
Preview in Slack
Status
Backlog
Priority
Medium
Assignee
Unassigned
As of today at 10:46 AM
Refresh
Open in Jira
✨ Summarise
Open in browser
Open
Share Bug JY-20776
View conversations
More actions
Today at 10:47:00 AM
10:47
проблем беше че няма pdf_url сега ще се разровя за конкретен репорт
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:47:35 AM
10:47
и идеята е на PHP да не го пробваме през час но Галя попита за регенериране
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:48:20 AM
10:48
предполагам че е нещо случайно най-вероятно само един репорт
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:50:30 AM
10:50
за бъдеще може да в самия пропхет има ли проверка дали има всичко преди да върне response, или пак от PHP да се провери преди пращане и да се пусне отново
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Steliyan Georgiev
Today at 10:51:18 AM
10:51 AM
може да направя профет ако няма pdf_url, да връща грешка за пхп?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 10:51:50 AM
10:51 AM
по-скоро да се регенерира
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 11:48:07 AM
11:48 AM
ето го и репорт
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
реално са два pdf и podcast и podcast си работи без проблем
image.png
Toggle file
image.png
Download image.png
Share file: image.png
View canvas details
More actions
1 reaction, react with +1 emoji
1
Add reaction…
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Processing uploaded file… complete! Message ready to be sent.
Channel...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":14,"bounds":{"left":0.36768618,"top":1.0,"width":0.011968086,"height":-0.058260202},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":21,"bounds":{"left":0.39893618,"top":1.0,"width":0.018284574,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":21,"bounds":{"left":0.39893618,"top":1.0,"width":0.01761968,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":21,"bounds":{"left":0.39893618,"top":1.0,"width":0.018284574,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":21,"bounds":{"left":0.39893618,"top":1.0,"width":0.02925532,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":21,"bounds":{"left":0.4504654,"top":1.0,"width":0.0026595744,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":21,"bounds":{"left":0.39893618,"top":1.0,"width":0.024268618,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.043882977,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.04454787,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.022273935,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.012300532,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.018284574,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.010638298,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.034574468,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.027593086,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"bounds":{"left":0.40425533,"top":1.0,"width":0.025930852,"height":-0.09177971},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":17,"bounds":{"left":0.46409574,"top":1.0,"width":0.030917553,"height":-0.09177971},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Add canvas","depth":18,"bounds":{"left":0.49601063,"top":1.0,"width":0.034242023,"height":-0.09177971},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Add canvas","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":17,"bounds":{"left":0.53125,"top":1.0,"width":0.020944148,"height":-0.09177971},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":19,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":17,"bounds":{"left":0.55352396,"top":1.0,"width":0.010638298,"height":-0.09177971},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":17,"on_screen":false,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":22,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:40:03 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:40 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"здрасти Стели","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:40:36 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:40","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Галя ме помоли да видим това със липсващ pdf_url на репорти","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:41:14 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:41","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"може ли по-късно да се чуем да видим какво може да направим","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Steliyan Georgiev","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:44:58 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:44 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Здрасти Лукаш, да","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"има ли тикет?","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:46:01 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:46 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://jiminny.atlassian.net/browse/JY-20776","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://jiminny.atlassian.net/browse/JY-20776","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Jira Cloud","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Remove preview","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Jira Cloud Bug JY-20776 Automated report - sentry Bug JY-20776 in Jira Cloud Preview in Slack Status Backlog Priority Medium Medium Assignee Unassigned Unassigned As of today at 10:46 AM Refresh Open in Jira ✨ Summarise","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Automated report - sentry","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Bug JY-20776 in Jira Cloud","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Preview in Slack","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Status","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Backlog","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Priority","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Medium","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Assignee","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unassigned","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"As of today at 10:46 AM","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Refresh","depth":27,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Jira","depth":27,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"✨ Summarise","depth":27,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Open in browser","depth":26,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Open","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Share Bug JY-20776","depth":26,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View conversations","depth":26,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More actions","depth":26,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 10:47:00 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:47","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"проблем беше че няма pdf_url сега ще се разровя за конкретен репорт","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 10:47:35 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:47","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"и идеята е на PHP да не го пробваме през час но Галя попита за регенериране","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 10:48:20 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:48","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"предполагам че е нещо случайно най-вероятно само един репорт","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 10:50:30 AM","depth":24,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:50","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"за бъдеще може да в самия пропхет има ли проверка дали има всичко преди да върне response, или пак от PHP да се провери преди пращане и да се пусне отново","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Steliyan Georgiev","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:51:18 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:51 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"може да направя профет ако няма pdf_url, да връща грешка за пхп?","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 10:51:50 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:51 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"по-скоро да се регенерира","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 11:48:07 AM","depth":23,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:48 AM","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ето го и репорт","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"реално са два pdf и podcast и podcast си работи без проблем","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"image.png","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":24,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Toggle file","depth":24,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"image.png","depth":26,"on_screen":true,"role_description":"Unlabelled image","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Download image.png","depth":27,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share file: image.png","depth":27,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View canvas details","depth":27,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"1 reaction, react with +1 emoji","depth":24,"on_screen":true,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Add reaction…","depth":24,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with white_check_mark","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":25,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":25,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"","depth":23,"on_screen":true,"value":"","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Processing uploaded file… complete! Message ready to be sent.","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Channel","depth":11,"on_screen":false,"role_description":"text"}]...
|
8944004944741689577
|
-5824959757741307814
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Steliyan Georgiev
Petko Kashinski
Galya Dimitrova
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Stoyan Tanev
Lukas Kovalik
you
Jira Cloud
Toast
Google Calendar
Messages
Messages
Add canvas
Add canvas
Files
Files
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Lukas Kovalik
Today at 10:40:03 AM
10:40 AM
здрасти Стели
Today at 10:40:36 AM
10:40
Галя ме помоли да видим това със липсващ pdf_url на репорти
Today at 10:41:14 AM
10:41
може ли по-късно да се чуем да видим какво може да направим
Steliyan Georgiev
Today at 10:44:58 AM
10:44 AM
Здрасти Лукаш, да
има ли тикет?
Lukas Kovalik
Today at 10:46:01 AM
10:46 AM
https://jiminny.atlassian.net/browse/JY-20776
https://jiminny.atlassian.net/browse/JY-20776
Jira Cloud
Jira Cloud
Remove preview
Jira Cloud Bug JY-20776 Automated report - sentry Bug JY-20776 in Jira Cloud Preview in Slack Status Backlog Priority Medium Medium Assignee Unassigned Unassigned As of today at 10:46 AM Refresh Open in Jira ✨ Summarise
Automated report - sentry
Bug JY-20776 in Jira Cloud
Preview in Slack
Status
Backlog
Priority
Medium
Assignee
Unassigned
As of today at 10:46 AM
Refresh
Open in Jira
✨ Summarise
Open in browser
Open
Share Bug JY-20776
View conversations
More actions
Today at 10:47:00 AM
10:47
проблем беше че няма pdf_url сега ще се разровя за конкретен репорт
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:47:35 AM
10:47
и идеята е на PHP да не го пробваме през час но Галя попита за регенериране
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:48:20 AM
10:48
предполагам че е нещо случайно най-вероятно само един репорт
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 10:50:30 AM
10:50
за бъдеще може да в самия пропхет има ли проверка дали има всичко преди да върне response, или пак от PHP да се провери преди пращане и да се пусне отново
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Steliyan Georgiev
Today at 10:51:18 AM
10:51 AM
може да направя профет ако няма pdf_url, да връща грешка за пхп?
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 10:51:50 AM
10:51 AM
по-скоро да се регенерира
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Lukas Kovalik
Today at 11:48:07 AM
11:48 AM
ето го и репорт
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
реално са два pdf и podcast и podcast си работи без проблем
image.png
Toggle file
image.png
Download image.png
Share file: image.png
View canvas details
More actions
1 reaction, react with +1 emoji
1
Add reaction…
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Processing uploaded file… complete! Message ready to be sent.
Channel
rirerox..TypeError: League|Flysystem\FileCloudWatch | us-east-2MIStOMbookmarksProtlles10019Windowhele• - app.jiminny.com/ai-reportsAl Reports• Report namel13-14 Aor. 2026• © Clear allQCoaching Profiles *e Jy 20820 es reindex stream mod(UY-20725) [HubSpot) Optimise CFJY-20725 add HS rate limit handlitPipelines - jiminny/app( Pull requests • jiminny/appWJY-207731 User Pilot not receivin!( JY-20773 fix user pilot tracking ofJY-207761 Automated report - seP Platform Sorint 2 02 . Platform Te( JY-20625 | JY-20742 | MCP РОС b— Data Sxoloren* (UY-20776] Automated report - ser8 Jiminny+ New TabNAME -() Coaching Profiles - 6 - 12 Apr 2026 - Client Success, UK Sales6д Coaching Profiles Podcast - 6 - 12 Apr 2026 - Client Success, UK Sales$You are currently impersonating Adelina Petrova <)FREQUENCY #WeeklyWeeklvSHAREDRASM.1BACN .‹ S0 lihl 1 Support Daily- in 3h 11m Б5* 100% C/2 & • Tue 12 May 11:49:25E@3 Ask Jiminny reportsDATE13/04/2026ACTIONS13/04/2026...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
20709
|
900
|
13
|
2026-05-11T16:12:27.090954+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778515947090_m1.jpg...
|
Notion Calendar
|
NULL
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APP ()DOCKERC SlackFileEditViewGoHistoryWindowHelp•APP ()DOCKERC &1DEV (docker)₴2APP (-zsh)-zsh+ScrmService->syncOpportunity('374720564');ScrmService->matchByName('Robot');end diffFixed 4 of 5666 files in 146.870 seconds, 60.00 MB memory usedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containerimage →Learn moreat [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20725-handle-HS-search-rate-lirdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.plPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminskiiuse humtostia anphconerfiuto.dist,plPHP runtime: 8.3.30Running analysis on 7 cores with 10 files per process.Parallel runner is an experimental feature and may be unstable, use it at your own risk.Loadedconfig default from".php-cs-fixer.dist.php".5666/5666 [100%Fixed 0 of 5666 files in 66.457 seconds, 60.00 MB memory usedWhat's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image →Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-lir•• 0ED→HomeDMsActivityFilesLaterMoreJiminny ...# contusion-clinic# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...^ Direct messagesP. Aneliya Angelova®. Galya Dimitrova I. Petko Kashinski&. Stefka StoyanovaVasil VasilevCo Nikolay IvanovAneliya Angelova, ...Stoyan Tanevo VesE. Lukas Kovalik y... O::: AppsS Jira CloudToast(abl100% <78• Mon 11 May 19:12:27Describe what you are looking for# releases8 22• MessagesC FilesBookmarks+9353b609 - JYToday ~bee402b7- JY-20395 Stop fetching ALLheaders (including file) for email importd6a0fe5a - JY-20395 lintdaa0373e - JY-20395 lint fixesaf59d609 - JY-20395 Removed no longer usedmethodsShow morejiminny/app| Added by GitHubCircleCl APP 5:11 PMDeployment Successful!Project: appWhen:05/11/202614:11:35Tag:View JobCircleCl APP 5:35 PMDeployment Successful!Project: appWhen:05/11/202614:35:58Tag:View JobMessage #releases..•...
|
NULL
|
8943718518592690004
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelp•APP ()DOCKERC SlackFileEditViewGoHistoryWindowHelp•APP ()DOCKERC &1DEV (docker)₴2APP (-zsh)-zsh+ScrmService->syncOpportunity('374720564');ScrmService->matchByName('Robot');end diffFixed 4 of 5666 files in 146.870 seconds, 60.00 MB memory usedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any containerimage →Learn moreat [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20725-handle-HS-search-rate-lirdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.plPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminskiiuse humtostia anphconerfiuto.dist,plPHP runtime: 8.3.30Running analysis on 7 cores with 10 files per process.Parallel runner is an experimental feature and may be unstable, use it at your own risk.Loadedconfig default from".php-cs-fixer.dist.php".5666/5666 [100%Fixed 0 of 5666 files in 66.457 seconds, 60.00 MB memory usedWhat's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image →Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-lir•• 0ED→HomeDMsActivityFilesLaterMoreJiminny ...# contusion-clinic# curiosity_lab# engineering# general# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...^ Direct messagesP. Aneliya Angelova®. Galya Dimitrova I. Petko Kashinski&. Stefka StoyanovaVasil VasilevCo Nikolay IvanovAneliya Angelova, ...Stoyan Tanevo VesE. Lukas Kovalik y... O::: AppsS Jira CloudToast(abl100% <78• Mon 11 May 19:12:27Describe what you are looking for# releases8 22• MessagesC FilesBookmarks+9353b609 - JYToday ~bee402b7- JY-20395 Stop fetching ALLheaders (including file) for email importd6a0fe5a - JY-20395 lintdaa0373e - JY-20395 lint fixesaf59d609 - JY-20395 Removed no longer usedmethodsShow morejiminny/app| Added by GitHubCircleCl APP 5:11 PMDeployment Successful!Project: appWhen:05/11/202614:11:35Tag:View JobCircleCl APP 5:35 PMDeployment Successful!Project: appWhen:05/11/202614:35:58Tag:View JobMessage #releases..•...
|
20707
|
NULL
|
NULL
|
NULL
|
|
22898
|
978
|
15
|
2026-05-12T07:25:53.901800+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778570753901_m1.jpg...
|
QuickTime Player
|
PLanhat Petko interest event 2026-05-11.mp4
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
02:55
toggle elapsed time, timecode and framecount
04:55
toggle duration and remaining time
document actions
PLanhat Petko interest event 2026-05-11.mp4...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"rewind","depth":1,"bounds":{"left":0.77569443,"top":0.9266667,"width":0.017361112,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"play/pause","depth":1,"bounds":{"left":0.8003472,"top":0.9172222,"width":0.02013889,"height":0.037777778},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"fast forward","depth":1,"bounds":{"left":0.828125,"top":0.9266667,"width":0.017361112,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"mute","depth":1,"bounds":{"left":0.65868056,"top":0.9266667,"width":0.015625,"height":0.016666668},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"More Controls","depth":1,"bounds":{"left":0.9496528,"top":0.9261111,"width":0.0125,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"toggle full screen","depth":1,"bounds":{"left":0.89340276,"top":0.93222225,"width":0.013888889,"height":0.022222223},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":1,"bounds":{"left":0.89340276,"top":0.9261111,"width":0.013888889,"height":0.022222223},"on_screen":true,"role_description":"button","is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":2,"bounds":{"left":0.89340276,"top":0.9261111,"width":0.013888889,"height":0.022222223},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show media selection menu","depth":1,"bounds":{"left":0.89340276,"top":0.93222225,"width":0.015277778,"height":0.022222223},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"toggle picture-in-picture playback","depth":1,"bounds":{"left":0.89340276,"top":0.92444444,"width":0.017361112,"height":0.022222223},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show action menu","depth":1,"bounds":{"left":0.89340276,"top":0.9316667,"width":0.014583333,"height":0.023333333},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"share","depth":1,"bounds":{"left":0.9232639,"top":0.9211111,"width":0.013541667,"height":0.025555555},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show chapter menu","depth":1,"bounds":{"left":0.89340276,"top":0.935,"width":0.014583333,"height":0.016666668},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"bounds":{"left":0.89340276,"top":0.93,"width":0.013888889,"height":0.026666667},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"bounds":{"left":0.89340276,"top":0.93277776,"width":0.017361112,"height":0.02111111},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"playback speed","depth":1,"bounds":{"left":0.89340276,"top":0.93277776,"width":0.013194445,"height":0.02111111},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"02:55","depth":1,"bounds":{"left":0.65868056,"top":0.9633333,"width":0.02638889,"height":0.016666668},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"toggle elapsed time, timecode and framecount","depth":1,"bounds":{"left":0.66006947,"top":0.9633333,"width":0.023611112,"height":0.016666668},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"04:55","depth":1,"bounds":{"left":0.9305556,"top":0.9633333,"width":0.031597223,"height":0.016666668},"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"toggle duration and remaining time","depth":1,"bounds":{"left":0.93194443,"top":0.9633333,"width":0.028819444,"height":0.016666668},"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"document actions","depth":1,"bounds":{"left":0.6097222,"top":0.04,"width":0.0069444445,"height":0.017777778},"on_screen":true,"role_description":"menu button","is_enabled":false,"is_focused":false},{"role":"AXStaticText","text":"PLanhat Petko interest event 2026-05-11.mp4","depth":1,"bounds":{"left":0.3986111,"top":0.04,"width":0.21111111,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
8941343663989620386
|
5922002151758629492
|
click
|
hybrid
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
02:55
toggle elapsed time, timecode and framecount
04:55
toggle duration and remaining time
document actions
PLanhat Petko interest event 2026-05-11.mp4
QuickTime PlayerFileEditViewWindowHelp(ablj Support Daily - in 4h 35 mA100% (8•Tue 12 May 10:25:53• PLanhat Petko interest event 2026-05-11.mp4Screen snareChromeFileEditViewHistoryBookmarksProfilesTabWindowHelp*Q8• Mon 11 May 12:20Gree!Scoreandre• wilsoCall AJiminM Inbox=N X=AppsBuildu User;§ New: | u User; | +Cws.planhat.com/jiminny/home/data-explorer/usagemetricdef?preview=UsageMetricDef.674543e45a4792694fe994e5© WorkAIKB• ChatPlayground Al...Apps+ CX Journey SMB....Jiminny ~Content ExplorerData ExplorerNotificationsEmail Manager•** MoreSections -ECS Day-to-day•Getting started GuideJust CS Data* Daily Operations# Weekly prepRenewals and UpsellRisk and Churn AnalyticsImplementation -Impl ProjectsTrial Opps (Under Review)Stoyan's clientsLeadershipSystem ReportsLeadership OperationsPortfolio Overview (Dashbo….NPS Report - GregClient Engagement OverviewRevenue AnalyticsJiminny - Calenda…M GMail• My Calendly - Eve....= PH New Ul LoginGet Starting with J...Chloe Onboarding..8 MetricB DatasetQ, PLAYBACK+ Metric6 nplaybackVisitedEnd Un]activities. playbackVisitedТугOverviewTrace- Company 2Fiter by Company• Playback Adoption Avg• Playback AdoptionEndUser• JUsers Playback AdoptionCal• Playback Adoption (Last 7 days)Cal• Playback AdoptionCalplaybackVisitedUsw1.2026w3 2026WS 2026#7 2026W8 2026WEEKSSUMNo groups foundAdd a comment40 +E. Petko6 Feb 11, 2026 - May 10, 2026w10 2026w11 2026w13 2026Al Notes: OffPetko ...reen .02:5404:55ve...
|
NULL
|
/Volumes/Work/2026/PLanhat Petko interest event 20 /Volumes/Work/2026/PLanhat Petko interest event 2026-05-11.mp4...
|
NULL
|
NULL
|
|
22899
|
979
|
8
|
2026-05-12T07:25:53.906657+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778570753906_m2.jpg...
|
QuickTime Player
|
PLanhat Petko interest event 2026-05-11.mp4
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
02:55
toggle elapsed time, timecode and framecount
04:55
toggle duration and remaining time
document actions
PLanhat Petko interest event 2026-05-11.mp4...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"rewind","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"play/pause","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"fast forward","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"mute","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"More Controls","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"toggle full screen","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":1,"on_screen":true,"role_description":"button","is_focused":false},{"role":"AXButton","text":"show external playback menu","depth":2,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show media selection menu","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"toggle picture-in-picture playback","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show action menu","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"share","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"show chapter menu","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXCheckBox","text":"zoom","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"playback speed","depth":1,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"02:55","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"toggle elapsed time, timecode and framecount","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"04:55","depth":1,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"toggle duration and remaining time","depth":1,"on_screen":true,"role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"document actions","depth":1,"bounds":{"left":0.5621675,"top":1.0,"width":0.0033244682,"height":-0.028730989},"on_screen":true,"role_description":"menu button","is_enabled":false,"is_focused":false},{"role":"AXStaticText","text":"PLanhat Petko interest event 2026-05-11.mp4","depth":1,"bounds":{"left":0.46110374,"top":1.0,"width":0.10106383,"height":-0.028730989},"on_screen":true,"role_description":"text"}]...
|
8941343663989620386
|
5922002151758629492
|
click
|
hybrid
|
NULL
|
rewind
play/pause
fast forward
mute
More Controls
rewind
play/pause
fast forward
mute
More Controls
toggle full screen
show external playback menu
show external playback menu
show media selection menu
toggle picture-in-picture playback
show action menu
share
show chapter menu
zoom
zoom
playback speed
02:55
toggle elapsed time, timecode and framecount
04:55
toggle duration and remaining time
document actions
PLanhat Petko interest event 2026-05-11.mp4
PnostormIavicatecode10019FV faVsco.js?9 JY-20725-handle-HS-search-rate-limitProjectyC) AutomatedReportGenerated.ong© PlaybackController.php(1) MatchDomainBvEmail© OpportunityActivityMa© OpportunitySyncStrate© OpportunitySyncStrate© ProspectCache.php€ ProspectSearchScope© ProspectSearchStrate© ProspectSearchStratec Providerkegistry.pnp€ RecordSelector.phpkesolvecompanyNamc) limererioaiterator.phaImoonuInternal0 Kioskv AutomatedReportsC) ActivitvivoeServiceC) AskJiminnvReportA(C) AutomatedRenortsiC) AutomatedRenorts.(C) DealStadesServiceC) RecioientsService.rE) ReportSort.ohrE) RenortSortDirection(C) KioskService.oho1M Mailom MeptinaGenerator1 NotificationM0Auth2M PecallAm SecurityD StrategyD Streaming_ leamD TelephonvD UserPilot0 Webhook© AbstractService.php© ActivityProviderFactorv.p© ActivitvService.php(C) AoiResoonseService.oho© ConferenceService.ohn(C)InsiahtSeatService.oho(C)InstantMeetinaService.oh(C)IntercomService.oho@ InapiClient.php©) IoaniService.ohr@ DarticinantShareService r 1201(@) PlanhatService nhr@ DlavhackService nhnYC) PlavhackViden@nlvServic 127(C) DlavbaskCotoaan.c.ndPlanhatService.php x:12 v.19 ^= laravel.l0gA SF (jiminny@localhost]4 HS_local [jiminny@localhost]# console [PRol)A console [STAGING][2026-05-07 14:21:15] local.INF0: [Hubspot] DEBUG Getting headers {"neaders.?"Vace".L"Inu,or May 2020 14.21.15 bMl"J,"Transter-Encodinq":"chunked").& console [EU]>0 lll 0support Dally • In 41 30mcascadePlanhat Event PlaybacThoughtsSearched planhat in ~/iminnylappThoughtsSearched olavback *visitedivisited."olavback in ~/fiminnvlaoo.100% 5• lue 1z May 10.20:0.+0 ..find planhat event playback visitedreaconly class Plannatservicepublic function -_constructtprivate Rolestatsrepository srolestatsrepository.) (...7/** othrows Guzzle xception */oubuc function trackuser suser strind Sevent, arrav soavload = : vord1f @ Sthis-›servicelsAvailable(Suser->getTeamO->qetPartnerido0)«return:Susen->inadld relations: "team!)Sdata =fInamel => Susen->aetNameollemaili = Susen->aptFmailAddnecs0ll'externalId' => $user->getUuid.'companyExternalId' => Suser->getTeam->getUuidaction = sevent,'info' => $payload.SplanhatResponse = Http::planhatAnaluticsApiO->post url:'analytics/'. confia( key:'services.planhat.tenantUuid'). Sdata):Sthis->loqFailedResponses(SplanhatResponse.message: METHOD"body => solanhatresponse->sonlo'status' => $planhatResponse->status@'data' => Sdatal** athrows Guzzesycention */nublic function meten(lisen Suser. string Sdimension. strina Svalue)• voidf...?** Athrows GuzzleSycention */public function upsertCompany(Team Steam): voidi...}* Athnowe Guz>leSycontion* @throws BindingResolutionExceptionpublic function upsertUser(User Suser): void{...}СОЛРРВРДВВВВВВВВВВВВВВВО"CF-Ray": ["9f80deb8db60dc3a-SOF"]."Strict-Transport-Security":["max-aqe=31536000: includeSubDomains: preload"]."server-timing": ["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\","x-content-tvoe-ootions":"nosniff"."x-hubsoot-correlation-id":"019e02d0-6fd8-7812-bdba-885b7ccb3ee3"7."Set-Cookie":["__cf_bm=StUrtdQgXVnik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfW07-May-26 14:51:15 GMT; domain=.hubapi.com; Http0nly; Secure; SameSite=None"),"Renont-To".f"s"endnoints".'\"url\":"https:\V/\V/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIx\"group\":\"cf-nel\",\"max_age\":604800}"],"NEL":["{\"success_fraction\":0.01,report to. "cr-nel,|"max agel ":604800}"]"Server": ["cloudflare"]}} {"correlation 1d":"95256555-ec98-4541-b9za-adta/sboyeab"."trace_10":"C/A8565-905t-4604-9405-0e5b551e5545',Soarched pl.Searched visited in aprThouahtslFound it in PlaybackController.php :Read DlavbackController.nhn #| 85-114Location:Plavbackcontroller.0no:95-168oho.*Cendina InlavbackVicitedl event to Planhat without clowina +hedeferlfn = Sthis->olanhatService-strack(user. Suserpay load:'activityld' = Sactivity->getId(),activitvluid' = sactivity-saetluido.-salwavs@)Contovt. Collod in tha niavhaak nado controllor wronnod in doforl to firo tha Dlanhat avant scunchronduely aftor thoHTTP response is sent to avoid slowina down the user experience.Oal *Jed daily usage quota is exhausted. Purchase extra usage to continue usina premium models. Quota resets May 12, 11:00"AN CUTYAsk anvthina (84L)÷ @Code SWF-1.620-17JUTE.Afo 4 spaces...
|
22897
|
/Volumes/Work/2026/PLanhat Petko interest event 20 /Volumes/Work/2026/PLanhat Petko interest event 2026-05-11.mp4...
|
NULL
|
NULL
|
|
25139
|
1054
|
11
|
2026-05-12T10:44:49.208425+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778582689208_m1.jpg...
|
Slack
|
platform-inner-team (Channel) - Jiminny Inc - 4 ne platform-inner-team (Channel) - Jiminny Inc - 4 new items - Slack...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Galya Dimitrova
Steliyan Georgiev
Petko Kashinski
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Stoyan Tanev
Lukas Kovalik
you
Toast
Jira Cloud
Google Calendar
Messages
Messages
Channel Overview
Channel Overview
Refinements
Refinements
Files
Files
Pins
Pins
Bookmarks
Bookmarks
Retro Action Items
Retro Action Items
Untitled
Untitled
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Today at 11:31:01 AM
11:31
второто число е potentially breaking change / new functionality
Today at 11:31:58 AM
11:31
ако правим промени, ще трябва да ги изнасяме в нова версия, и старата да може да работи паралелно
Today at 11:32:07 AM
11:32
или да сме много внимателни да не са breaking changes
Nikolay Yankov
Today at 11:34:43 AM
11:34 AM
replied to a thread:
но така ако нормален юзър си е кънектнал нашия MCP и си чати, ще трябва при всяка промяна пак да го кънектва ли?
Aко имаме versioning и докато клиента си чати, ние пуснем нова версия, би трябвало той да може да продължи да ползва старата версия на нешето API и ние да продължим да връщаме по стария начин за него и всичко да му е наред. Това означава ние да поддържаме стари версии на API-то. N на брой.
Ако нямаме versioning и докато си чати пуснем нови неща, той ще продължи да си ползва без да разбира, че има промяна и може да започне да му връща кофти неща и да не разбира от какво е. Чак след като си започне нов чат, ще е up to date с нас.
View newer replies
Today at 11:35:47 AM
11:35
Да, точно, ще трябва старите версии да продължават да работят
Today at 11:35:59 AM
11:35
И да имаме deprecation механизъм
Galya Dimitrova
Today at 11:37:16 AM
11:37 AM
не знам дали това няма да е объркващо. Аз си държа чатовете на дадена тема месеци наред за да си ползвам историята. То така ако трябва да почвам нов всеки път ще ми замине всичко и ще трябва от начало.
Nikolay Nikolov
Today at 11:37:42 AM
11:37 AM
По добре да нямаме breaking changes, и да го направим като Атена - нещо да връща коя е текущата , но само 1 да работи ?
Nikolay Yankov
Today at 11:37:51 AM
11:37 AM
Тук са дадени best practices
https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers
https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers
Model Context Protocol
Versioning Published MCP Servers - Model Context Protocol
Versioning Published MCP Servers - Model Context Protocol
(21 kB)
image
Versioning Published MCP Servers - Model Context Protocol
Galya Dimitrova
Today at 11:37:56 AM
11:37 AM
все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори
4 replies
Last reply today at 11:45 AM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…
Save for later
Summarize thread
More actions
Vasil Vasilev
Today at 11:39:42 AM
11:39 AM
И аз подозирам, че периодичко рефшрешва
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 11:40:06 AM
11:40
точно заради такива потребители дето отварят една сесия, пишат нещо, зарязват я, след една седмица се връщат на нея
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Nikolay Yankov
Today at 11:40:21 AM
11:40 AM
replied to a thread:
все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори
има нотификации в спека на MCP
notifications/tools/list_changed
примерно този и ние можем да го имплементираме да казваме на клиентите, е има нова версия
https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification
https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification
Model Context Protocol
Resources - Model Context Protocol
Resources - Model Context Protocol
(21 kB)
image
Resources - Model Context Protocol
View newer replies
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
View thread
Forward message…
Save for later
More actions
Vasil Vasilev
Today at 11:41:05 AM
11:41 AM
аз очаквам, че по скоро при повторно отваряне / рефреш на чата агента ще провери версията, и просто ще си ребилдне контекста преди да го прати
8 replies
Last reply today at 1:24 PM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…
Save for later
Summarize thread
More actions
Lukas Kovalik
Today at 1:44:44 PM
1:44 PM
може ли един approve
https://github.com/jiminny/app/pull/12024
https://github.com/jiminny/app/pull/12024
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Processing uploaded file… complete! Message ready to be sent.
Channel platform-inner-team...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":14,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":21,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":21,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":21,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"support","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Ivanov","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Nikolay Yankov","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Stoyan Tanev","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Lukas Kovalik","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"you","depth":23,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Toast","depth":23,"bounds":{"left":0.56805557,"top":0.0,"width":0.025694445,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Jira Cloud","depth":23,"bounds":{"left":0.56805557,"top":0.0,"width":0.045833334,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Google Calendar","depth":23,"bounds":{"left":0.56805557,"top":0.0,"width":0.06388889,"height":0.012222222},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Messages","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Messages","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Channel Overview","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Channel Overview","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Refinements","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Refinements","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Pins","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Pins","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Bookmarks","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Bookmarks","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Retro Action Items","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Retro Action Items","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Untitled","depth":18,"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Untitled","depth":20,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Add and Edit Channel Tabs","depth":18,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Canvas","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"List","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Folder","depth":18,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"Jump to date","depth":24,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 11:31:01 AM","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:31","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"второто число е potentially breaking change / new functionality","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:31:58 AM","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:31","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ако правим промени, ще трябва да ги изнасяме в нова версия, и старата да може да работи паралелно","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:32:07 AM","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:32","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"или да сме много внимателни да не са breaking changes","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Nikolay Yankov","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:34:43 AM","depth":25,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:34 AM","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"replied to a thread:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"но така ако нормален юзър си е кънектнал нашия MCP и си чати, ще трябва при всяка промяна пак да го кънектва ли?","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Aко имаме versioning и докато клиента си чати, ние пуснем нова версия, би трябвало той да може да продължи да ползва старата версия на нешето API и ние да продължим да връщаме по стария начин за него и всичко да му е наред. Това означава ние да поддържаме стари версии на API-то. N на брой.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Ако нямаме versioning и докато си чати пуснем нови неща, той ще продължи да си ползва без да разбира, че има промяна и може да започне да му връща кофти неща и да не разбира от какво е. Чак след като си започне нов чат, ще е up to date с нас.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"View newer replies","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 11:35:47 AM","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:35","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Да, точно, ще трябва старите версии да продължават да работят","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:35:59 AM","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:35","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"И да имаме deprecation механизъм","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Galya Dimitrova","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:37:16 AM","depth":25,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:37 AM","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"не знам дали това няма да е объркващо. Аз си държа чатовете на дадена тема месеци наред за да си ползвам историята. То така ако трябва да почвам нов всеки път ще ми замине всичко и ще трябва от начало.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Nikolay Nikolov","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:37:42 AM","depth":25,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:37 AM","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"По добре да нямаме breaking changes, и да го направим като Атена - нещо да връща коя е текущата , но само 1 да работи ?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Nikolay Yankov","depth":25,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"Today at 11:37:51 AM","depth":25,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:37 AM","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Тук са дадени best practices","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model Context Protocol","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Versioning Published MCP Servers - Model Context Protocol","depth":27,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Versioning Published MCP Servers - Model Context Protocol","depth":28,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"(21 kB)","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"image","depth":27,"on_screen":true,"help_text":"collapse","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Versioning Published MCP Servers - Model Context Protocol","depth":29,"on_screen":true,"help_text":"Versioning Published MCP Servers - Model Context Protocol","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Galya Dimitrova","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 11:37:56 AM","depth":25,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:37 AM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"4 replies","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Last reply today at 11:45 AM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"View thread","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply to thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Summarize thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Vasil Vasilev","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 11:39:42 AM","depth":25,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:39 AM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"И аз подозирам, че периодичко рефшрешва","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Today at 11:40:06 AM","depth":26,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:40","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"точно заради такива потребители дето отварят една сесия, пишат нещо, зарязват я, след една седмица се връщат на нея","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Nikolay Yankov","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 11:40:21 AM","depth":25,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:40 AM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"replied to a thread:","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"има нотификации в спека на MCP","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"notifications/tools/list_changed","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"примерно този и ние можем да го имплементираме да казваме на клиентите, е има нова версия","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification","depth":26,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Model Context Protocol","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Resources - Model Context Protocol","depth":27,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Resources - Model Context Protocol","depth":28,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"(21 kB)","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"image","depth":27,"on_screen":true,"help_text":"collapse","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Resources - Model Context Protocol","depth":29,"on_screen":true,"help_text":"Resources - Model Context Protocol","role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View newer replies","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Vasil Vasilev","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 11:41:05 AM","depth":25,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"11:41 AM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"аз очаквам, че по скоро при повторно отваряне / рефреш на чата агента ще провери версията, и просто ще си ребилдне контекста преди да го прати","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"8 replies","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Last reply today at 1:24 PM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"View thread","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply to thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Summarize thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Lukas Kovalik","depth":25,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":25,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"Today at 1:44:44 PM","depth":25,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1:44 PM","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"може ли един approve","depth":26,"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"https://github.com/jiminny/app/pull/12024","depth":26,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://github.com/jiminny/app/pull/12024","depth":27,"on_screen":true,"role_description":"text"},{"role":"AXCheckBox","text":"React with white_check_mark","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with eyes","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"React with raised_hands","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Add reaction…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Reply in thread","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Forward message…","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Save for later","depth":27,"on_screen":false,"role_description":"toggle button","subrole":"AXToggleButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More actions","depth":27,"on_screen":false,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"","depth":24,"on_screen":true,"value":"","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Processing uploaded file… complete! Message ready to be sent.","depth":11,"bounds":{"left":0.4798611,"top":0.0,"width":0.047916666,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Channel platform-inner-team","depth":11,"bounds":{"left":0.4798611,"top":0.0,"width":0.041666668,"height":0.0011111111},"on_screen":true,"role_description":"text"}]...
|
8940489018176743620
|
-6005095426662160310
|
visual_change
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Galya Dimitrova
Steliyan Georgiev
Petko Kashinski
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev
Nikolay Ivanov
Aneliya Angelova
,
Nikolay Yankov
,
Steliyan Georgiev
Stoyan Tanev
Lukas Kovalik
you
Toast
Jira Cloud
Google Calendar
Messages
Messages
Channel Overview
Channel Overview
Refinements
Refinements
Files
Files
Pins
Pins
Bookmarks
Bookmarks
Retro Action Items
Retro Action Items
Untitled
Untitled
Add and Edit Channel Tabs
Canvas
List
Folder
Jump to date
Today at 11:31:01 AM
11:31
второто число е potentially breaking change / new functionality
Today at 11:31:58 AM
11:31
ако правим промени, ще трябва да ги изнасяме в нова версия, и старата да може да работи паралелно
Today at 11:32:07 AM
11:32
или да сме много внимателни да не са breaking changes
Nikolay Yankov
Today at 11:34:43 AM
11:34 AM
replied to a thread:
но така ако нормален юзър си е кънектнал нашия MCP и си чати, ще трябва при всяка промяна пак да го кънектва ли?
Aко имаме versioning и докато клиента си чати, ние пуснем нова версия, би трябвало той да може да продължи да ползва старата версия на нешето API и ние да продължим да връщаме по стария начин за него и всичко да му е наред. Това означава ние да поддържаме стари версии на API-то. N на брой.
Ако нямаме versioning и докато си чати пуснем нови неща, той ще продължи да си ползва без да разбира, че има промяна и може да започне да му връща кофти неща и да не разбира от какво е. Чак след като си започне нов чат, ще е up to date с нас.
View newer replies
Today at 11:35:47 AM
11:35
Да, точно, ще трябва старите версии да продължават да работят
Today at 11:35:59 AM
11:35
И да имаме deprecation механизъм
Galya Dimitrova
Today at 11:37:16 AM
11:37 AM
не знам дали това няма да е объркващо. Аз си държа чатовете на дадена тема месеци наред за да си ползвам историята. То така ако трябва да почвам нов всеки път ще ми замине всичко и ще трябва от начало.
Nikolay Nikolov
Today at 11:37:42 AM
11:37 AM
По добре да нямаме breaking changes, и да го направим като Атена - нещо да връща коя е текущата , но само 1 да работи ?
Nikolay Yankov
Today at 11:37:51 AM
11:37 AM
Тук са дадени best practices
https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers
https://modelcontextprotocol.io/registry/versioning#versioning-published-mcp-servers
Model Context Protocol
Versioning Published MCP Servers - Model Context Protocol
Versioning Published MCP Servers - Model Context Protocol
(21 kB)
image
Versioning Published MCP Servers - Model Context Protocol
Galya Dimitrova
Today at 11:37:56 AM
11:37 AM
все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори
4 replies
Last reply today at 11:45 AM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…
Save for later
Summarize thread
More actions
Vasil Vasilev
Today at 11:39:42 AM
11:39 AM
И аз подозирам, че периодичко рефшрешва
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Today at 11:40:06 AM
11:40
точно заради такива потребители дето отварят една сесия, пишат нещо, зарязват я, след една седмица се връщат на нея
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Nikolay Yankov
Today at 11:40:21 AM
11:40 AM
replied to a thread:
все си мисля че АИя би трябвало да рефрешва схемите/версиите и при отворен чат който стои дълго. Ще е странно да чака нов да се отвори
има нотификации в спека на MCP
notifications/tools/list_changed
примерно този и ние можем да го имплементираме да казваме на клиентите, е има нова версия
https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification
https://modelcontextprotocol.io/specification/draft/server/resources#list-changed-notification
Model Context Protocol
Resources - Model Context Protocol
Resources - Model Context Protocol
(21 kB)
image
Resources - Model Context Protocol
View newer replies
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
View thread
Forward message…
Save for later
More actions
Vasil Vasilev
Today at 11:41:05 AM
11:41 AM
аз очаквам, че по скоро при повторно отваряне / рефреш на чата агента ще провери версията, и просто ще си ребилдне контекста преди да го прати
8 replies
Last reply today at 1:24 PM
View thread
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply to thread
Forward message…
Save for later
Summarize thread
More actions
Lukas Kovalik
Today at 1:44:44 PM
1:44 PM
може ли един approve
https://github.com/jiminny/app/pull/12024
https://github.com/jiminny/app/pull/12024
React with white_check_mark
React with eyes
React with raised_hands
Add reaction…
Reply in thread
Forward message…
Save for later
More actions
Processing uploaded file… complete! Message ready to be sent.
Channel platform-inner-team
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpDOCKER₴81DEV (-zsh)-O $2APP (-zsh)>0.ec2-user@ip-10-30-129-190:~883ec2-user@ip-10-30-129-...84• Support Daily - in 1 h 16 m-zshX5screenpipe"Fordocumentation,visit [URL_WITH_CREDENTIALS] ~]$ dockerexec-it $(dockerps--format "{{.ID}}" --filter "name=ecs-worker" | head -1) /bin/bash -c "cd /home/jiminny && bash"root@a3efaa2235c4:/home/jiminny# php artisantinkerPsy Shellv0.12.21CPHP8.3.30cli) by Justin HilemanNew PHPmanualis available (latest:3.0.5).Update with"doc --update-manual'> Sresult = AutomatedReportResult::find(1872);[!] Aliasing'AutomatedReportResult' to'Jiminny)Models\AutomatedReportResult' for this Tinker session.Jiminny\Models\AutomatedReportResult{#15863id: 1872,uuid: b"CO-0,/a\e¢Ht°ão11",report_id:54,name: "Coaching Profiles - 6 - 12 Apr 2026 - Client Success, UK Sales",media_type: "pdf",parent_id: null,5100% C78• Tue 12 May 13:44:48181O ₴6-zsh870r-PS-416-Firefox2fa41b-ata3-43a9-az48-86bUe36t3131_podcast.mp5"1_podcast.ssml"}"requested_at:'"2026-04-1301:00:57"generated_at:"2026-04-1301:11:48",sent_at: null,created_at:"2026-04-1301:00:27"updated_at: "2026-04-1301:11:48"Sresult->status = 4;Sresult->saveO);trueexitINFOGoodbye.root@a3efaa2235c4:/home/jiminny# U$I31Nlidl"poacast_ssml_url":"s3:VVJ1minny.Cl1ent-dataVStUt4810-/e/(-4086-8f69-934ZYae4a/Ub VreportsV8ZZta41b-ata3-43a9-aZ48-86bUe36t313...
|
25137
|
NULL
|
NULL
|
NULL
|
|
10511
|
476
|
12
|
2026-05-08T17:31:15.324662+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778261475324_m2.jpg...
|
Code
|
docker-compose.yml — docker [SSH: nas] — Modified
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
dawarich
dsk-uploader
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
n8n
notifier-app
npm
oauth
obsidian
ollama
open-webui
openttd
openvpn-client
orchestrator
outfit-app
owntracks-stack
paperlessngx
payments-logger
.claude
backend
frontend
.env
.env.example
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
Review payment logger au…
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
Screen Reader Optimized
expanded
Clear
Review payment logger authorization implementation
Session history
New session
Message actions
review payment_logger project how toes authorization work
Thinking
Thinking
Let me explore the payment_logger project structure first.
Bash
Find payment_logger project files
IN
find /volume2/docker -type f -name "*.py" -o -name "*.yml" -o -name "*.yaml" -o -name "*.env" -o -name "*.toml" -o -name "*.cfg" | grep -i payment | head -50
Copy code to clipboard
OUT
bfs: error: /volume2/docker/portnotedb/pg_commit_ts: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_dynshmem: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_notify: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_serial: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_snapshots: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_subtrans: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_twophase: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_multixact: Permission denied.
bfs: error: /volume2/docker/portnotedb/base: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_replslot: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_tblspc: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_stat: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_stat_tmp: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_xact: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_logical: Permission denied.
bfs: error: /volume2/docker/openttd/.dbus: Permission denied.
bfs: error: /volume2/docker/immich/db: Permission denied.
bfs: error: /volume2/docker/paperlessngx/db: Permission denied.
bfs: error: /volume2/docker/appflowy/postgres: Permission denied.
bfs: error: /volume2/docker/auth/db: Permission denied.
bfs: error: /volume2/docker/openttd/.XDG/dbus-1: Permission denied.
bfs: error: /volume2/docker/openttd/.config/pulse: Permission denied.
bfs: error: /volume2/docker/openttd/.cache/openbox/sessions: Permission denied.
/volume2/docker/payments-logger/.env
/volume2/docker/payments-logger/docker-compose.yml
/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml
Bash
List docker directory contents
IN
ls /volume2/docker/
Copy code to clipboard
OUT
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
beszel
bitwarden
dawarich
dsk-uploader
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
mariadb
meeting-detector
mindfulmama
n8n
notifier-app
npm
oauth
obsidian
ollama
openttd
openvpn-client
open-webui
orchestrator
outfit-app
owntracks-stack
paperlessngx
payments-logger
personal-log
personal-log-system
player
portainer
portnotedb
reminders-app
romm
second-brain
static
stirling
syncthing
tailscale
temp-logger
test-app
tmp
torrent-stack
tree
trilium
tubearchivist
twenty
uptimekuma
ytptube
Agent:
Explore payment_logger authorization code
IN
Explore the payments-logger project at /volume2/docker/payments-logger and give me a thorough understanding of how authorization works. I need to know:
1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)
2. Where auth is enforced (middleware, decorators, route guards, etc.)
3. What roles/permissions exist
4. How tokens are issued, validated, and refreshed
5. Any auth-related config (env vars, settings)
Please read the key source files and give me a clear summary with file paths and line numbers for the important parts.
Bash
IN
find /volume2/docker/payments-logger -type f -name "*.py" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" | grep -E "(auth|token|jwt|session|permission|role)" | head -20
Copy code to clipboard
OUT
(Bash completed with no output)
Bash
IN
ls -la /volume2/docker/payments-logger/ 2>/dev/null || echo "Directory not found"
Copy code to clipboard
OUT
total 36
drwxrwxrwx 1 Adm1n admin 162 Mar 29 18:01 .
drwxrwxrwx 1 root root 1146 Apr 26 18:49 ..
-rwxrwxrwx 1 Adm1n admin 15233 Feb 22 17:08 API.md
drwxrwxrwx 1 Adm1n admin 88 Mar 29 15:41 backend
drwxrwxrwx 1 Adm1n admin 38 Mar 29 17:07 .claude
-rwxrwxrwx 1 Adm1n admin 1502 Mar 29 18:01 docker-compose.yml
-rwxrwxrwx 1 Adm1n admin 1066 Mar 29 15:44 .env
-rwxrwxrwx 1 Adm1n admin 1078 Feb 14 17:53 .env.example
drwxrwxrwx 1 Adm1n admin 194 Mar 29 15:44 frontend
-rwxrwxrwx 1 Adm1n admin 19 Feb 14 17:54 .gitignore
-rwxrwxrwx 1 Adm1n admin 4052 Feb 14 19:06 README.md
Bash
IN
find /volume2/docker/payments-logger/backend -type f | head -30
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/Dockerfile
/volume2/docker/payments-logger/backend/.dockerignore
/volume2/docker/payments-logger/backend/package.json
/volume2/docker/payments-logger/backend/prisma/schema.prisma
/volume2/docker/payments-logger/backend/src/parser.js
/volume2/docker/payments-logger/backend/src/index.js
/volume2/docker/payments-logger/backend/src/auth.js
/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml
/volume2/docker/payments-logger/backend/src/routes/payments.js
/volume2/docker/payments-logger/backend/prisma/migrations/20260210_init/migration.sql
/volume2/docker/payments-logger/backend/prisma/migrations/20260210_simplify/migration.sql
/volume2/docker/payments-logger/backend/prisma/migrations/20260222_add_card/migration.sql
Bash
IN
find /volume2/docker/payments-logger -type f \( -name "*.js" -o -name "*.json" -o -name "*.md" \) | sort
Copy code to clipboard
OUT
/volume2/docker/payments-logger/API.md
/volume2/docker/payments-logger/backend/package.json
/volume2/docker/payments-logger/backend/src/auth.js
/volume2/docker/payments-logger/backend/src/index.js
/volume2/docker/payments-logger/backend/src/parser.js
/volume2/docker/payments-logger/backend/src/routes/payments.js
/volume2/docker/payments-logger/.claude/settings.local.json
/volume2/docker/payments-logger/frontend/package.json
/volume2/docker/payments-logger/frontend/postcss.config.js
/volume2/docker/payments-logger/frontend/src/auth.js
/volume2/docker/payments-logger/frontend/tailwind.config.js
/volume2/docker/payments-logger/frontend/vite.config.js
/volume2/docker/payments-logger/README.md
Read
.env
.env
Read
auth.js
auth.js
Read
index.js
index.js
Read
package.json
package.json
Read
auth.js
auth.js
Read
API.md
API.md
Read
payments.js
payments.js
Read
.env.example
.env.example
Read
schema.prisma
schema.prisma
Bash
IN
grep -r "API_KEY\|auth\|token\|oauth\|hydra" /volume2/docker/payments-logger/backend/src --include="*.js" -n
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/src/index.js:6:const { bearerTokenMiddleware, wellKnownRouter } = require('./auth');
/volume2/docker/payments-logger/backend/src/index.js:18:// ── OAuth Bearer token auth (skips public paths) ──────────────────────────────
/volume2/docker/payments-logger/backend/src/auth.js:2: * OAuth 2.1 / Hydra JWT authentication for Express.
/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints
/volume2/docker/payments-logger/backend/src/auth.js:33:function sendUnauthorized(res, detail, error = 'invalid_token') {
/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata="${realm}/.well-known/oauth-protected-resource"`,
/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server="${HYDRA_PUBLIC_URL}"`,
/volume2/docker/payments-logger/backend/src/auth.js:52: * On success, attaches the decoded payload to req.tokenPayload.
/volume2/docker/payments-logger/backend/src/auth.js:54: * @param {Set<string>} publicPaths Paths to skip auth for.
/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',
/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',
/volume2/docker/payments-logger/backend/src/auth.js:66: const auth = req.headers.authorization || '';
/volume2/docker/payments-logger/backend/src/auth.js:67: if (!auth.toLowerCase().startsWith('bearer ')) {
/volume2/docker/payments-logger/backend/src/auth.js:68: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');
/volume2/docker/payments-logger/backend/src/auth.js:71: const token = auth.slice(7).trim();
/volume2/docker/payments-logger/backend/src/auth.js:72: if (!token) {
/volume2/docker/payments-logger/backend/src/auth.js:73: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');
/volume2/docker/payments-logger/backend/src/auth.js:82: const { payload } = await jwtVerify(token, getJwks(), verifyOptions);
/volume2/docker/payments-logger/backend/src/auth.js:83: req.tokenPayload = payload;
/volume2/docker/payments-logger/backend/src/auth.js:87: const error = msg.includes('expired') ? 'invalid_token' : 'invalid_token';
/volume2/docker/payments-logger/backend/src/auth.js:88: sendUnauthorized(res, msg, error);
/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],
/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:119: authorization_endpoint: doc.authorization_endpoint,
/volume2/docker/payments-logger/backend/src/auth.js:120: token_endpoint: doc.token_endpoint,
/volume2/docker/payments-logger/backend/src/auth.js:123: grant_types_supported: doc.grant_types_supported || ['authorization_code', 'refresh_token'],
/volume2/docker/payments-logger/backend/src/auth.js:124: [API_KEY]: doc.[API_KEY] || ['none', 'client_secret_basic'],
/volume2/docker/payments-logger/backend/src/auth.js:128: id_token_signing_alg_values_supported: doc.id_token_signing_alg_values_supported || ['RS256'],
/volume2/docker/payments-logger/backend/src/routes/payments.js:58:// ── Ingest a payment (public — no auth) ──────────────────────────────────────
Bash
IN
find /volume2/docker/payments-logger/frontend/src -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | head -20
Copy code to clipboard
OUT
/volume2/docker/payments-logger/frontend/src/main.jsx
/volume2/docker/payments-logger/frontend/src/App.jsx
/volume2/docker/payments-logger/frontend/src/auth.js
/volume2/docker/payments-logger/frontend/src/components/PaymentCard.jsx
/volume2/docker/payments-logger/frontend/src/components/PaymentList.jsx
/volume2/docker/payments-logger/frontend/src/components/FilterBar.jsx
/volume2/docker/payments-logger/frontend/src/components/PaymentTable.jsx
/volume2/docker/payments-logger/frontend/src/components/AuthProvider.jsx
/volume2/docker/payments-logger/frontend/src/components/AuthCallback.jsx
Read
AuthProvider.jsx
AuthProvider.jsx
Read
AuthCallback.jsx
AuthCallback.jsx
Read
App.jsx
App.jsx
Read
main.jsx
main.jsx
Read
vite.config.js
vite.config.js
Bash
IN
grep -r "HYDRA\|VITE" /volume2/docker/payments-logger --include=".env*" -n
Copy code to clipboard
OUT
(Bash completed with no output)
Read
docker-compose.yml
docker-compose.yml
Bash
IN
grep -r "API_KEY" /volume2/docker/payments-logger/backend --include="*.js" -n
Copy code to clipboard
OUT
(Bash completed with no output)
Read
README.md
README.md
Bash
IN
grep -n "HYDRA\|oauth\|TOKEN" /volume2/docker/payments-logger/backend/src/index.js /volume2/docker/payments-logger/backend/src/auth.js | head -40
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints
/volume2/docker/payments-logger/backend/src/auth.js:11:const HYDRA_PUBLIC_URL = (process.env.HYDRA_PUBLIC_URL || '').replace(/\/$/, '');
/volume2/docker/payments-logger/backend/src/auth.js:24: new URL(`${HYDRA_PUBLIC_URL}/.well-known/jwks.json`)
/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata="${realm}/.well-known/oauth-protected-resource"`,
/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server="${HYDRA_PUBLIC_URL}"`,
/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',
/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',
/volume2/docker/payments-logger/backend/src/auth.js:78: issuer: HYDRA_PUBLIC_URL,
/volume2/docker/payments-logger/backend/src/auth.js:81: if (process.env.TOKEN_AUDIENCE) verifyOptions.audience = process.env.TOKEN_AUDIENCE;
/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],
/volume2/docker/payments-logger/backend/src/auth.js:107: jwks_uri: `${HYDRA_PUBLIC_URL}/.well-known/jwks.json`,
/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:113: const r = await fetch(`${HYDRA_PUBLIC_URL}/.well-known/openid-configuration`);
/volume2/docker/payments-logger/backend/src/auth.js:118: issuer: doc.issuer || HYDRA_PUBLIC_URL,
Thinking
Thinking
payments-logger Authorization Overview
payments-logger Authorization Overview
The project uses
OAuth 2.1 with PKCE
via an external Hydra OAuth server at
hydra.lakylak.xyz
.
Auth Flow
Auth Flow
Frontend
(
frontend/src/auth.js
frontend/src/auth.js
):
Fetches OAuth server metadata via discovery endpoint
Generates a PKCE code verifier/challenge (SHA-256)
Redirects user to Hydra login with
code_challenge
On callback, exchanges code + verifier for tokens
Stores
access_token
,
refresh_token
in
localStorage
(
pl_oauth_tokens
)
Auto-refreshes token 30 seconds before expiry
Backend
(
backend/src/auth.js
backend/src/auth.js
):
bearerTokenMiddleware
validates the
Authorization: Bearer <token>
header
Fetches Hydra's JWKS (cached 300s) and verifies JWT signature (RS256/ES256/PS256)
Validates issuer (
https://hydra.lakylak.xyz
) and optional audience
Enforcement
Enforcement
Applied globally in
backend/src/index.js:19
backend/src/index.js:19
with these
public (unprotected) paths
:
/.well-known/oauth-protected-resource
/.well-known/oauth-authorization-server
/api/health
/api/payments/ingest
(rate-limited at 200 req/min — for SMS ingestion)
Roles/Permissions
Roles/Permissions
No RBAC
— it's binary: authenticated vs. unauthenticated. Required scopes:
openid
,
offline_access
.
Key Security Notes
Key Security Notes
PKCE
prevents authorization code interception
State parameter
provides CSRF protection
No server-side token storage
— validation is stateless via JWKS
The README/API docs mention an
X-API-Key
header, but that's
outdated
— the actual code uses Bearer tokens only
Config lives in
docker-compose.yml
docker-compose.yml
(lines 30–33):
HYDRA_PUBLIC_URL
,
REQUIRED_SCOPES
,
OAUTH_CLIENT_ID
, and optionally
TOKEN_AUDIENCE
/
JWKS_CACHE_TTL
.
Message actions
compare it to location logger and make it the same. Lets remove hydra and only use authentic (or whatever location logger uses).
Thinking
Thinking
Let me first explore how location-logger handles authorization.
Agent:
Explore location-logger authorization code
IN
Explore the location-logger project at /volume2/docker/location-logger and give me a thorough understanding of how authorization works. I need to know:
1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)
2. Where auth is enforced (middleware, decorators, route guards, etc.)
3. What roles/permissions exist
4. How tokens are issued, validated, and refreshed
5. Any auth-related config (env vars, settings)
6. Frontend auth flow (how the user logs in, how tokens are stored and used)
7. The full directory/file structure of the project
Please read ALL relevant source files completely and give me a detailed summary with file paths and line numbers. I need enough detail to replicate this auth system in another project.
Bash
IN
find /volume2/docker/location-logger -type f -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.json" | head -50
Copy code to clipboard
OUT
/volume2/docker/location-logger/.mcp.json
/volume2/docker/location-logger/app/__init__.py
/volume2/docker/location-logger/app/utils.py
/volume2/docker/location-logger/app/config.py
/volume2/docker/location-logger/app/main.py
/volume2/docker/location-logger/alembic/env.py
/volume2/docker/location-logger/mcp-server/server.py
/volume2/docker/location-logger/alembic/versions/20260203_201500_create_location_points_table.py
/volume2/docker/location-logger/alembic/versions/20260209_200000_add_enhanced_fields_and_tables.py
/volume2/docker/location-logger/alembic/versions/20260214_000000_add_users_and_audit_logs.py
/volume2/docker/location-logger/alembic/versions/20260320_000000_add_audit_query_string.py
/volume2/docker/location-logger/app/services/geofence_service.py
/volume2/docker/location-logger/app/services/recorder_service.py
/volume2/docker/location-logger/app/services/trip_service.py
/volume2/docker/location-logger/app/services/enhanced_location_service.py
/volume2/docker/location-logger/app/services/geocoding_service.py
/volume2/docker/location-logger/app/clients/__init__.py
/volume2/docker/location-logger/app/clients/dawarich_client.py
/volume2/docker/location-logger/app/clients/owntracks_client.py
/volume2/docker/location-logger/app/models/areas.py
/volume2/docker/location-logger/app/models/geofence_events.py
/volume2/docker/location-logger/app/models/trips.py
/volume2/docker/location-logger/app/models/user.py
/volume2/docker/location-logger/app/models/location.py
/volume2/docker/location-logger/app/models/audit_log.py
/volume2/docker/location-logger/app/auth/__init__.py
/volume2/docker/location-logger/app/auth/dependencies.py
/volume2/docker/location-logger/app/auth/utils.py
/volume2/docker/location-logger/app/api/enhanced_endpoints.py
/volume2/docker/location-logger/app/db/base.py
/volume2/docker/location-logger/app/db/session.py
✽
Sussing...
Queue another message…
Queue another message…
Add
Show command menu (/)
docker-compose.yml
docker-compose.yml
Edit automatically
Edit automatically...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Explorer (⇧⌘E)","depth":19,"bounds":{"left":0.0,"top":0.047885075,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":true},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.057462092,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Search (⇧⌘F)","depth":19,"bounds":{"left":0.0,"top":0.08619314,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.09577015,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Source Control (⌃⇧G) - 55 pending changes","depth":19,"bounds":{"left":0.0,"top":0.1245012,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.13407822,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"55","depth":22,"bounds":{"left":0.0076462766,"top":0.1452514,"width":0.0043218085,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Run and Debug (⇧⌘D)","depth":19,"bounds":{"left":0.0,"top":0.16280925,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.17238627,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Remote Explorer","depth":19,"bounds":{"left":0.0,"top":0.20111732,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.21069433,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Extensions (⇧⌘X) - 2 require update","depth":19,"bounds":{"left":0.0,"top":0.23942538,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":22,"bounds":{"left":0.0039893617,"top":0.2490024,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":22,"bounds":{"left":0.009640957,"top":0.2601756,"width":0.0019946808,"height":0.008778931},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Claude Code","depth":19,"bounds":{"left":0.0,"top":0.27773345,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Containers","depth":19,"bounds":{"left":0.0,"top":0.3160415,"width":0.015957447,"height":0.03830806},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"EXPLORER","depth":17,"bounds":{"left":0.022606382,"top":0.047885075,"width":0.018949468,"height":0.02793296},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"EXPLORER","depth":18,"bounds":{"left":0.022606382,"top":0.056664005,"width":0.018949468,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.022606382,"top":0.056664005,"width":0.0023271276,"height":0.0103751}},{"char_start":1,"char_count":7,"bounds":{"left":0.024933511,"top":0.056664005,"width":0.01662234,"height":0.0103751}}],"role_description":"text"},{"role":"AXButton","text":"Explorer Section: docker [SSH: nas]","depth":21,"bounds":{"left":0.015957447,"top":0.07581804,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Explorer Section: docker [SSH: nas]","depth":22,"bounds":{"left":0.022606382,"top":0.07581804,"width":0.038231384,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"DOCKER [SSH: NAS]","depth":23,"bounds":{"left":0.022606382,"top":0.079010375,"width":0.038231384,"height":0.0103751},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.022606382,"top":0.07980846,"width":0.0029920214,"height":0.0103751}},{"char_start":1,"char_count":16,"bounds":{"left":0.025598405,"top":0.07980846,"width":0.03523936,"height":0.0103751}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.0933759,"width":0.005319149,"height":0.0071827616},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dawarich","depth":27,"bounds":{"left":0.025930852,"top":0.0933759,"width":0.017952127,"height":0.006384677},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.105347164,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"dsk-uploader","depth":27,"bounds":{"left":0.025930852,"top":0.105347164,"width":0.026928192,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.10614525,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.028590426,"top":0.10614525,"width":0.024268618,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.10614525,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.12290503,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"flask-app","depth":27,"bounds":{"left":0.025930852,"top":0.12290503,"width":0.018949468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.123703115,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.027593086,"top":0.123703115,"width":0.017287234,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.123703115,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.14046289,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"garmin-connector","depth":27,"bounds":{"left":0.025930852,"top":0.14046289,"width":0.036236703,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.14126097,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":15,"bounds":{"left":0.028590426,"top":0.14126097,"width":0.033909574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.15802075,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"gitea","depth":27,"bounds":{"left":0.025930852,"top":0.15802075,"width":0.009973404,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.15881884,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.028590426,"top":0.15881884,"width":0.00731383,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.17557861,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health","depth":27,"bounds":{"left":0.025930852,"top":0.17557861,"width":0.012300532,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.1763767,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.1763767,"width":0.009973404,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.19313647,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"health-tracker","depth":27,"bounds":{"left":0.025930852,"top":0.19313647,"width":0.028590426,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.19393456,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.028590426,"top":0.19393456,"width":0.025930852,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.21069433,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"homarr","depth":27,"bounds":{"left":0.025930852,"top":0.21069433,"width":0.014295213,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.21149242,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.21149242,"width":0.011968086,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.22825219,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"hst","depth":27,"bounds":{"left":0.025930852,"top":0.22825219,"width":0.0063164895,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.22905028,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":2,"bounds":{"left":0.028590426,"top":0.22905028,"width":0.003656915,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.24581006,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"immich","depth":27,"bounds":{"left":0.025930852,"top":0.24581006,"width":0.01462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.24660814,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.026928192,"top":0.24660814,"width":0.013630319,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.26336792,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jellyfinht","depth":27,"bounds":{"left":0.025930852,"top":0.26336792,"width":0.016954787,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.264166,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.026928192,"top":0.264166,"width":0.016289894,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.28092578,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"kavita","depth":27,"bounds":{"left":0.025930852,"top":0.28092578,"width":0.011635638,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.28172386,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.02825798,"top":0.28172386,"width":0.009640957,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.29848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"libreoffice","depth":27,"bounds":{"left":0.025930852,"top":0.29848364,"width":0.020279255,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.29928172,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.026928192,"top":0.29928172,"width":0.019281914,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.3160415,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"linkwarden","depth":27,"bounds":{"left":0.025930852,"top":0.3160415,"width":0.021609042,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.31683958,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.026928192,"top":0.31683958,"width":0.020611702,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.33359936,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":27,"bounds":{"left":0.025930852,"top":0.33359936,"width":0.030917553,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.33439744,"width":0.0009973404,"height":0.011971269}},{"char_start":1,"char_count":14,"bounds":{"left":0.026928192,"top":0.33439744,"width":0.029920213,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.33439744,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.35115722,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic","depth":27,"bounds":{"left":0.028590426,"top":0.35115722,"width":0.015625,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.3519553,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.030917553,"top":0.3519553,"width":0.013297873,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.3519553,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.36871508,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"app","depth":27,"bounds":{"left":0.028590426,"top":0.36871508,"width":0.0076462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.36951315,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":2,"bounds":{"left":0.030917553,"top":0.36951315,"width":0.005319149,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.10605053,"top":0.36951315,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.38627294,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mcp-server","depth":27,"bounds":{"left":0.028590426,"top":0.38627294,"width":0.023271276,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.38707104,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.032247342,"top":0.38707104,"width":0.019946808,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.40223464,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"bounds":{"left":0.028590426,"top":0.4038308,"width":0.00831117,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.4046289,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":3,"bounds":{"left":0.029920213,"top":0.4046289,"width":0.006981383,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.4197925,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"bounds":{"left":0.028590426,"top":0.42138866,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.42218676,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.029920213,"top":0.42218676,"width":0.024933511,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.43735036,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".gitignore","depth":27,"bounds":{"left":0.028590426,"top":0.43894652,"width":0.018949468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.43974462,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.029920213,"top":0.43974462,"width":0.017952127,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.45490822,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".mcp.json","depth":27,"bounds":{"left":0.028590426,"top":0.45650437,"width":0.019614361,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.45730248,"width":0.0013297872,"height":0.011971269}},{"char_start":1,"char_count":8,"bounds":{"left":0.029920213,"top":0.45730248,"width":0.018284574,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.45730248,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.47246608,"width":0.0076462766,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alembic.ini","depth":27,"bounds":{"left":0.028590426,"top":0.47406226,"width":0.021609042,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.47486034,"width":0.0023271276,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.030917553,"top":0.47486034,"width":0.019281914,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.49002394,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"docker-compose.yml","depth":27,"bounds":{"left":0.028590426,"top":0.49162012,"width":0.042220745,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.4924182,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":17,"bounds":{"left":0.03125,"top":0.4924182,"width":0.03956117,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.4924182,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.50758183,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Dockerfile","depth":27,"bounds":{"left":0.028590426,"top":0.509178,"width":0.020611702,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.509976,"width":0.0033244682,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.031914894,"top":0.509976,"width":0.017287234,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.5251397,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"README.md","depth":27,"bounds":{"left":0.028590426,"top":0.52673584,"width":0.025265958,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.5275339,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.54269755,"width":0.0076462766,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"requirements.txt","depth":27,"bounds":{"left":0.028590426,"top":0.5442937,"width":0.032912236,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.5450918,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":15,"bounds":{"left":0.03025266,"top":0.5450918,"width":0.03158245,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"M","depth":27,"bounds":{"left":0.10638298,"top":0.5450918,"width":0.003656915,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.5602554,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"today_map.html","depth":27,"bounds":{"left":0.028590426,"top":0.56185156,"width":0.032247342,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.028590426,"top":0.56264967,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.03025266,"top":0.56264967,"width":0.030585106,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.5794094,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mariadb","depth":27,"bounds":{"left":0.025930852,"top":0.5794094,"width":0.016289894,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.5802075,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.029587766,"top":0.5802075,"width":0.012965426,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.5969673,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"meeting-detector","depth":27,"bounds":{"left":0.025930852,"top":0.5969673,"width":0.03557181,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.5977654,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":15,"bounds":{"left":0.029587766,"top":0.5977654,"width":0.031914894,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.61452514,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"mindfulmama","depth":27,"bounds":{"left":0.025930852,"top":0.61452514,"width":0.027260639,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.61532325,"width":0.003656915,"height":0.011971269}},{"char_start":1,"char_count":10,"bounds":{"left":0.029587766,"top":0.61532325,"width":0.023603724,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.632083,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"n8n","depth":27,"bounds":{"left":0.025930852,"top":0.632083,"width":0.0076462766,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.6328811,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":2,"bounds":{"left":0.028590426,"top":0.6328811,"width":0.004986702,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.64964086,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"notifier-app","depth":27,"bounds":{"left":0.025930852,"top":0.64964086,"width":0.023603724,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.65043896,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.028590426,"top":0.65043896,"width":0.020944148,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.6671987,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"npm","depth":27,"bounds":{"left":0.025930852,"top":0.6671987,"width":0.008976064,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.6847566,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"oauth","depth":27,"bounds":{"left":0.025930852,"top":0.6847566,"width":0.011303191,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.6855547,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.028590426,"top":0.6855547,"width":0.008976064,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.70231444,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"obsidian","depth":27,"bounds":{"left":0.025930852,"top":0.70231444,"width":0.016954787,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.70311254,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":7,"bounds":{"left":0.028590426,"top":0.70311254,"width":0.014295213,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.7198723,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ollama","depth":27,"bounds":{"left":0.025930852,"top":0.7198723,"width":0.012965426,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.7206704,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":5,"bounds":{"left":0.028590426,"top":0.7206704,"width":0.010638298,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.73743016,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"open-webui","depth":27,"bounds":{"left":0.025930852,"top":0.73743016,"width":0.023936171,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.73822826,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.028590426,"top":0.73822826,"width":0.021276595,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.754988,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"openttd","depth":27,"bounds":{"left":0.025930852,"top":0.754988,"width":0.015625,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.7557861,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":6,"bounds":{"left":0.028590426,"top":0.7557861,"width":0.013297873,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.7725459,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"openvpn-client","depth":27,"bounds":{"left":0.025930852,"top":0.7725459,"width":0.030585106,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.773344,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.028590426,"top":0.773344,"width":0.027925532,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.79010373,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"orchestrator","depth":27,"bounds":{"left":0.025930852,"top":0.79010373,"width":0.024933511,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.79090184,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.028590426,"top":0.79090184,"width":0.022273935,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.8076616,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"outfit-app","depth":27,"bounds":{"left":0.025930852,"top":0.8076616,"width":0.020279255,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.8084597,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":9,"bounds":{"left":0.028590426,"top":0.8084597,"width":0.01761968,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.82521945,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"owntracks-stack","depth":27,"bounds":{"left":0.025930852,"top":0.82521945,"width":0.03357713,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.82601756,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":14,"bounds":{"left":0.028590426,"top":0.82601756,"width":0.030917553,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.8427773,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"paperlessngx","depth":27,"bounds":{"left":0.025930852,"top":0.8427773,"width":0.026928192,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.8435754,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":11,"bounds":{"left":0.028590426,"top":0.8435754,"width":0.024268618,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.019614361,"top":0.8603352,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"payments-logger","depth":27,"bounds":{"left":0.025930852,"top":0.8603352,"width":0.034574468,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.025930852,"top":0.8611333,"width":0.0026595744,"height":0.011971269}},{"char_start":1,"char_count":14,"bounds":{"left":0.028590426,"top":0.8611333,"width":0.031914894,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.87789303,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".claude","depth":27,"bounds":{"left":0.028590426,"top":0.87789303,"width":0.01462766,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.8954509,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":27,"bounds":{"left":0.028590426,"top":0.8954509,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"bounds":{"left":0.022273935,"top":0.91300875,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"frontend","depth":27,"bounds":{"left":0.028590426,"top":0.91300875,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.92897046,"width":0.0063164895,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env","depth":27,"bounds":{"left":0.028590426,"top":0.93056667,"width":0.00831117,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.021276595,"top":0.9465283,"width":0.0063164895,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".env.example","depth":27,"bounds":{"left":0.028590426,"top":0.9465283,"width":0.025930852,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Outline Section","depth":21,"bounds":{"left":0.015957447,"top":0.9473264,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.9497207,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"OUTLINE","depth":22,"bounds":{"left":0.022606382,"top":0.9473264,"width":0.01662234,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"OUTLINE","depth":23,"bounds":{"left":0.022606382,"top":0.95131683,"width":0.01662234,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Timeline Section","depth":21,"bounds":{"left":0.015957447,"top":0.9648843,"width":0.09940159,"height":0.017557861},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.01662234,"top":0.96727854,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"TIMELINE","depth":22,"bounds":{"left":0.022606382,"top":0.9648843,"width":0.01761968,"height":0.017557861},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"TIMELINE","depth":23,"bounds":{"left":0.022606382,"top":0.9688747,"width":0.01761968,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Review payment logger au…","depth":28,"bounds":{"left":0.11569149,"top":0.047885075,"width":0.07679521,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXRadioButton","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"bounds":{"left":0.5578458,"top":0.047885075,"width":0.06815159,"height":0.02793296},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"","depth":29,"bounds":{"left":0.5940825,"top":0.07821229,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"docker-compose.yml, preview, Editor Group 2","depth":28,"on_screen":false,"role_description":"editor","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"remote SSH: nas","depth":16,"bounds":{"left":0.0006648936,"top":0.98244214,"width":0.028590426,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.0033244682,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SSH: nas","depth":17,"bounds":{"left":0.008643617,"top":0.9856345,"width":0.017952127,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git)","depth":16,"bounds":{"left":0.030917553,"top":0.98244214,"width":0.03756649,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.031914894,"top":0.9848364,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"location-logger","depth":17,"bounds":{"left":0.03723404,"top":0.9856345,"width":0.03025266,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - main*, Checkout Branch/Tag...","depth":16,"bounds":{"left":0.06815159,"top":0.98244214,"width":0.019614361,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.069148935,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"main*","depth":17,"bounds":{"left":0.07446808,"top":0.9856345,"width":0.012300532,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"location-logger (Git) - Synchronize Changes","depth":16,"bounds":{"left":0.08743351,"top":0.98244214,"width":0.0076462766,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"No Problems","depth":16,"bounds":{"left":0.09740692,"top":0.98244214,"width":0.022606382,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.09906915,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.1043883,"top":0.9856345,"width":0.004986702,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.109042555,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.1143617,"top":0.9856345,"width":0.0039893617,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"No Ports Forwarded","depth":16,"bounds":{"left":0.12167553,"top":0.98244214,"width":0.012632979,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.12333777,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":17,"bounds":{"left":0.12865691,"top":0.9856345,"width":0.0039893617,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Notifications","depth":16,"bounds":{"left":0.9886968,"top":0.98244214,"width":0.010638298,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sign In","depth":16,"bounds":{"left":0.9650931,"top":0.98244214,"width":0.022606382,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"","depth":17,"bounds":{"left":0.96675533,"top":0.9848364,"width":0.0056515955,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sign In","depth":17,"bounds":{"left":0.97207445,"top":0.9856345,"width":0.013962766,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Compose","depth":16,"bounds":{"left":0.9424867,"top":0.98244214,"width":0.020279255,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions","depth":16,"bounds":{"left":0.93517286,"top":0.98244214,"width":0.0076462766,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"LF","depth":16,"bounds":{"left":0.92486703,"top":0.98244214,"width":0.007978723,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"UTF-8","depth":16,"bounds":{"left":0.90724736,"top":0.98244214,"width":0.015625,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Spaces: 2","depth":16,"bounds":{"left":0.88331115,"top":0.98244214,"width":0.022273935,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Ln 1, Col 1","depth":16,"bounds":{"left":0.85771275,"top":0.98244214,"width":0.023936171,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screen Reader Optimized","depth":16,"bounds":{"left":0.80352396,"top":0.98244214,"width":0.05319149,"height":0.01755786},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"expanded","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Clear","depth":12,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Review payment logger authorization implementation","depth":19,"bounds":{"left":0.11801862,"top":0.08060654,"width":0.099734046,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Session history","depth":19,"bounds":{"left":0.5359042,"top":0.08060654,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"Session history","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New session","depth":19,"bounds":{"left":0.5465425,"top":0.08060654,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"New session","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Message actions","depth":24,"bounds":{"left":0.54787236,"top":0.11173184,"width":0.0066489363,"height":0.015961692},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"review payment_logger project how toes authorization work","depth":25,"bounds":{"left":0.125,"top":0.12529927,"width":0.12034574,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Thinking","depth":23,"on_screen":false,"role_description":"disclosure triangle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thinking","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Let me explore the payment_logger project structure first.","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Find payment_logger project files","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker -type f -name \"*.py\" -o -name \"*.yml\" -o -name \"*.yaml\" -o -name \"*.env\" -o -name \"*.toml\" -o -name \"*.cfg\" | grep -i payment | head -50","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"bfs: error: /volume2/docker/portnotedb/pg_commit_ts: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_dynshmem: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_notify: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_serial: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_snapshots: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_subtrans: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_twophase: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_multixact: Permission denied.\nbfs: error: /volume2/docker/portnotedb/base: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_replslot: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_tblspc: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_stat: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_stat_tmp: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_xact: Permission denied.\nbfs: error: /volume2/docker/portnotedb/pg_logical: Permission denied.\nbfs: error: /volume2/docker/openttd/.dbus: Permission denied.\nbfs: error: /volume2/docker/immich/db: Permission denied.\nbfs: error: /volume2/docker/paperlessngx/db: Permission denied.\nbfs: error: /volume2/docker/appflowy/postgres: Permission denied.\nbfs: error: /volume2/docker/auth/db: Permission denied.\nbfs: error: /volume2/docker/openttd/.XDG/dbus-1: Permission denied.\nbfs: error: /volume2/docker/openttd/.config/pulse: Permission denied.\nbfs: error: /volume2/docker/openttd/.cache/openbox/sessions: Permission denied.\n/volume2/docker/payments-logger/.env\n/volume2/docker/payments-logger/docker-compose.yml\n/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"List docker directory contents","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ls /volume2/docker/","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"adguard\nai-stack\napp-db\nappflowy\naudiobookshelf\nauth\nbeszel\nbitwarden\ndawarich\ndsk-uploader\nflask-app\ngarmin-connector\ngitea\nhealth\nhealth-tracker\nhomarr\nhst\nimmich\njellyfinht\nkavita\nlibreoffice\nlinkwarden\nlocation-logger\nmariadb\nmeeting-detector\nmindfulmama\nn8n\nnotifier-app\nnpm\noauth\nobsidian\nollama\nopenttd\nopenvpn-client\nopen-webui\norchestrator\noutfit-app\nowntracks-stack\npaperlessngx\npayments-logger\npersonal-log\npersonal-log-system\nplayer\nportainer\nportnotedb\nreminders-app\nromm\nsecond-brain\nstatic\nstirling\nsyncthing\ntailscale\ntemp-logger\ntest-app\ntmp\ntorrent-stack\ntree\ntrilium\ntubearchivist\ntwenty\nuptimekuma\nytptube","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Agent:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Explore payment_logger authorization code","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Explore the payments-logger project at /volume2/docker/payments-logger and give me a thorough understanding of how authorization works. I need to know:\n\n1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)\n2. Where auth is enforced (middleware, decorators, route guards, etc.)\n3. What roles/permissions exist\n4. How tokens are issued, validated, and refreshed\n5. Any auth-related config (env vars, settings)\n\nPlease read the key source files and give me a clear summary with file paths and line numbers for the important parts.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker/payments-logger -type f -name \"*.py\" -o -name \"*.json\" -o -name \"*.yml\" -o -name \"*.yaml\" | grep -E \"(auth|token|jwt|session|permission|role)\" | head -20","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(Bash completed with no output)","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ls -la /volume2/docker/payments-logger/ 2>/dev/null || echo \"Directory not found\"","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"total 36\ndrwxrwxrwx 1 Adm1n admin 162 Mar 29 18:01 .\ndrwxrwxrwx 1 root root 1146 Apr 26 18:49 ..\n-rwxrwxrwx 1 Adm1n admin 15233 Feb 22 17:08 API.md\ndrwxrwxrwx 1 Adm1n admin 88 Mar 29 15:41 backend\ndrwxrwxrwx 1 Adm1n admin 38 Mar 29 17:07 .claude\n-rwxrwxrwx 1 Adm1n admin 1502 Mar 29 18:01 docker-compose.yml\n-rwxrwxrwx 1 Adm1n admin 1066 Mar 29 15:44 .env\n-rwxrwxrwx 1 Adm1n admin 1078 Feb 14 17:53 .env.example\ndrwxrwxrwx 1 Adm1n admin 194 Mar 29 15:44 frontend\n-rwxrwxrwx 1 Adm1n admin 19 Feb 14 17:54 .gitignore\n-rwxrwxrwx 1 Adm1n admin 4052 Feb 14 19:06 README.md","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker/payments-logger/backend -type f | head -30","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/payments-logger/backend/Dockerfile\n/volume2/docker/payments-logger/backend/.dockerignore\n/volume2/docker/payments-logger/backend/package.json\n/volume2/docker/payments-logger/backend/prisma/schema.prisma\n/volume2/docker/payments-logger/backend/src/parser.js\n/volume2/docker/payments-logger/backend/src/index.js\n/volume2/docker/payments-logger/backend/src/auth.js\n/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml\n/volume2/docker/payments-logger/backend/src/routes/payments.js\n/volume2/docker/payments-logger/backend/prisma/migrations/20260210_init/migration.sql\n/volume2/docker/payments-logger/backend/prisma/migrations/20260210_simplify/migration.sql\n/volume2/docker/payments-logger/backend/prisma/migrations/20260222_add_card/migration.sql","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker/payments-logger -type f \\( -name \"*.js\" -o -name \"*.json\" -o -name \"*.md\" \\) | sort","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/payments-logger/API.md\n/volume2/docker/payments-logger/backend/package.json\n/volume2/docker/payments-logger/backend/src/auth.js\n/volume2/docker/payments-logger/backend/src/index.js\n/volume2/docker/payments-logger/backend/src/parser.js\n/volume2/docker/payments-logger/backend/src/routes/payments.js\n/volume2/docker/payments-logger/.claude/settings.local.json\n/volume2/docker/payments-logger/frontend/package.json\n/volume2/docker/payments-logger/frontend/postcss.config.js\n/volume2/docker/payments-logger/frontend/src/auth.js\n/volume2/docker/payments-logger/frontend/tailwind.config.js\n/volume2/docker/payments-logger/frontend/vite.config.js\n/volume2/docker/payments-logger/README.md","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":".env","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":".env","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"auth.js","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"auth.js","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"index.js","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"index.js","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"package.json","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"package.json","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"auth.js","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"auth.js","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"API.md","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"API.md","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"payments.js","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"payments.js","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":".env.example","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":".env.example","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"schema.prisma","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"schema.prisma","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -r \"API_KEY\\|auth\\|token\\|oauth\\|hydra\" /volume2/docker/payments-logger/backend/src --include=\"*.js\" -n","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/payments-logger/backend/src/index.js:6:const { bearerTokenMiddleware, wellKnownRouter } = require('./auth');\n/volume2/docker/payments-logger/backend/src/index.js:18:// ── OAuth Bearer token auth (skips public paths) ──────────────────────────────\n/volume2/docker/payments-logger/backend/src/auth.js:2: * OAuth 2.1 / Hydra JWT authentication for Express.\n/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints\n/volume2/docker/payments-logger/backend/src/auth.js:33:function sendUnauthorized(res, detail, error = 'invalid_token') {\n/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata=\"${realm}/.well-known/oauth-protected-resource\"`,\n/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server=\"${HYDRA_PUBLIC_URL}\"`,\n/volume2/docker/payments-logger/backend/src/auth.js:52: * On success, attaches the decoded payload to req.tokenPayload.\n/volume2/docker/payments-logger/backend/src/auth.js:54: * @param {Set<string>} publicPaths Paths to skip auth for.\n/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',\n/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',\n/volume2/docker/payments-logger/backend/src/auth.js:66: const auth = req.headers.authorization || '';\n/volume2/docker/payments-logger/backend/src/auth.js:67: if (!auth.toLowerCase().startsWith('bearer ')) {\n/volume2/docker/payments-logger/backend/src/auth.js:68: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');\n/volume2/docker/payments-logger/backend/src/auth.js:71: const token = auth.slice(7).trim();\n/volume2/docker/payments-logger/backend/src/auth.js:72: if (!token) {\n/volume2/docker/payments-logger/backend/src/auth.js:73: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');\n/volume2/docker/payments-logger/backend/src/auth.js:82: const { payload } = await jwtVerify(token, getJwks(), verifyOptions);\n/volume2/docker/payments-logger/backend/src/auth.js:83: req.tokenPayload = payload;\n/volume2/docker/payments-logger/backend/src/auth.js:87: const error = msg.includes('expired') ? 'invalid_token' : 'invalid_token';\n/volume2/docker/payments-logger/backend/src/auth.js:88: sendUnauthorized(res, msg, error);\n/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {\n/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],\n/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {\n/volume2/docker/payments-logger/backend/src/auth.js:119: authorization_endpoint: doc.authorization_endpoint,\n/volume2/docker/payments-logger/backend/src/auth.js:120: token_endpoint: doc.token_endpoint,\n/volume2/docker/payments-logger/backend/src/auth.js:123: grant_types_supported: doc.grant_types_supported || ['authorization_code', 'refresh_token'],\n/volume2/docker/payments-logger/backend/src/auth.js:124: token_endpoint_auth_methods_supported: doc.token_endpoint_auth_methods_supported || ['none', 'client_secret_basic'],\n/volume2/docker/payments-logger/backend/src/auth.js:128: id_token_signing_alg_values_supported: doc.id_token_signing_alg_values_supported || ['RS256'],\n/volume2/docker/payments-logger/backend/src/routes/payments.js:58:// ── Ingest a payment (public — no auth) ──────────────────────────────────────","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker/payments-logger/frontend/src -type f -name \"*.js\" -o -name \"*.jsx\" -o -name \"*.ts\" -o -name \"*.tsx\" | head -20","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/payments-logger/frontend/src/main.jsx\n/volume2/docker/payments-logger/frontend/src/App.jsx\n/volume2/docker/payments-logger/frontend/src/auth.js\n/volume2/docker/payments-logger/frontend/src/components/PaymentCard.jsx\n/volume2/docker/payments-logger/frontend/src/components/PaymentList.jsx\n/volume2/docker/payments-logger/frontend/src/components/FilterBar.jsx\n/volume2/docker/payments-logger/frontend/src/components/PaymentTable.jsx\n/volume2/docker/payments-logger/frontend/src/components/AuthProvider.jsx\n/volume2/docker/payments-logger/frontend/src/components/AuthCallback.jsx","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"AuthProvider.jsx","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"AuthProvider.jsx","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"AuthCallback.jsx","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"AuthCallback.jsx","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"App.jsx","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"App.jsx","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"main.jsx","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"main.jsx","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"vite.config.js","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"vite.config.js","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -r \"HYDRA\\|VITE\" /volume2/docker/payments-logger --include=\".env*\" -n","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(Bash completed with no output)","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"docker-compose.yml","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"docker-compose.yml","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -r \"API_KEY\" /volume2/docker/payments-logger/backend --include=\"*.js\" -n","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(Bash completed with no output)","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Read","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"README.md","depth":24,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"README.md","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"grep -n \"HYDRA\\|oauth\\|TOKEN\" /volume2/docker/payments-logger/backend/src/index.js /volume2/docker/payments-logger/backend/src/auth.js | head -40","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"on_screen":false,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints\n/volume2/docker/payments-logger/backend/src/auth.js:11:const HYDRA_PUBLIC_URL = (process.env.HYDRA_PUBLIC_URL || '').replace(/\\/$/, '');\n/volume2/docker/payments-logger/backend/src/auth.js:24: new URL(`${HYDRA_PUBLIC_URL}/.well-known/jwks.json`)\n/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata=\"${realm}/.well-known/oauth-protected-resource\"`,\n/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server=\"${HYDRA_PUBLIC_URL}\"`,\n/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',\n/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',\n/volume2/docker/payments-logger/backend/src/auth.js:78: issuer: HYDRA_PUBLIC_URL,\n/volume2/docker/payments-logger/backend/src/auth.js:81: if (process.env.TOKEN_AUDIENCE) verifyOptions.audience = process.env.TOKEN_AUDIENCE;\n/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {\n/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],\n/volume2/docker/payments-logger/backend/src/auth.js:107: jwks_uri: `${HYDRA_PUBLIC_URL}/.well-known/jwks.json`,\n/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {\n/volume2/docker/payments-logger/backend/src/auth.js:113: const r = await fetch(`${HYDRA_PUBLIC_URL}/.well-known/openid-configuration`);\n/volume2/docker/payments-logger/backend/src/auth.js:118: issuer: doc.issuer || HYDRA_PUBLIC_URL,","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Thinking","depth":23,"on_screen":false,"role_description":"disclosure triangle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thinking","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"payments-logger Authorization Overview","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"payments-logger Authorization Overview","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The project uses","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"OAuth 2.1 with PKCE","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"via an external Hydra OAuth server at","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hydra.lakylak.xyz","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Auth Flow","depth":22,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Auth Flow","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Frontend","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"frontend/src/auth.js","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"frontend/src/auth.js","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"):","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Fetches OAuth server metadata via discovery endpoint","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Generates a PKCE code verifier/challenge (SHA-256)","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Redirects user to Hydra login with","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"code_challenge","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"On callback, exchanges code + verifier for tokens","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Stores","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"access_token","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"refresh_token","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"in","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"localStorage","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"pl_oauth_tokens","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":")","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Auto-refreshes token 30 seconds before expiry","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Backend","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"backend/src/auth.js","depth":23,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"backend/src/auth.js","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"):","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"bearerTokenMiddleware","depth":25,"bounds":{"left":0.14261968,"top":0.11731844,"width":0.04920213,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"validates the","depth":24,"bounds":{"left":0.19281915,"top":0.11572227,"width":0.02825798,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Authorization: Bearer <token>","depth":25,"bounds":{"left":0.22240691,"top":0.11731844,"width":0.06815159,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"header","depth":24,"bounds":{"left":0.29155585,"top":0.11572227,"width":0.015625,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Fetches Hydra's JWKS (cached 300s) and verifies JWT signature (RS256/ES256/PS256)","depth":24,"bounds":{"left":0.14128989,"top":0.13168396,"width":0.17852394,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Validates issuer (","depth":24,"bounds":{"left":0.14128989,"top":0.14684756,"width":0.034242023,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"https://hydra.lakylak.xyz","depth":25,"bounds":{"left":0.1768617,"top":0.14844373,"width":0.05851064,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":") and optional audience","depth":24,"bounds":{"left":0.23670213,"top":0.14684756,"width":0.047539894,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Enforcement","depth":22,"bounds":{"left":0.13264628,"top":0.18515563,"width":0.41855052,"height":0.018355945},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"Enforcement","depth":23,"bounds":{"left":0.13264628,"top":0.1867518,"width":0.03158245,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Applied globally in","depth":23,"bounds":{"left":0.13264628,"top":0.21707901,"width":0.037898935,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"backend/src/index.js:19","depth":23,"bounds":{"left":0.1705452,"top":0.21707901,"width":0.04720745,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"backend/src/index.js:19","depth":24,"bounds":{"left":0.1705452,"top":0.21707901,"width":0.04720745,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"with these","depth":23,"bounds":{"left":0.21775267,"top":0.21707901,"width":0.023271276,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"public (unprotected) paths","depth":24,"bounds":{"left":0.24102394,"top":0.21707901,"width":0.05817819,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":":","depth":23,"bounds":{"left":0.29886967,"top":0.21707901,"width":0.0013297872,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/.well-known/oauth-protected-resource","depth":25,"bounds":{"left":0.14261968,"top":0.2442139,"width":0.08676862,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/.well-known/oauth-authorization-server","depth":25,"bounds":{"left":0.14261968,"top":0.25937748,"width":0.09142287,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/api/health","depth":25,"bounds":{"left":0.14261968,"top":0.2753392,"width":0.025598405,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/api/payments/ingest","depth":25,"bounds":{"left":0.14261968,"top":0.29130086,"width":0.046875,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"(rate-limited at 200 req/min — for SMS ingestion)","depth":24,"bounds":{"left":0.19049202,"top":0.2897047,"width":0.10073138,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Roles/Permissions","depth":22,"bounds":{"left":0.13264628,"top":0.32801276,"width":0.41855052,"height":0.018355945},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"Roles/Permissions","depth":23,"bounds":{"left":0.13264628,"top":0.32960895,"width":0.045212764,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"No RBAC","depth":24,"bounds":{"left":0.13264628,"top":0.35993615,"width":0.019281914,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"— it's binary: authenticated vs. unauthenticated. Required scopes:","depth":23,"bounds":{"left":0.15159574,"top":0.35993615,"width":0.13597074,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"openid","depth":24,"bounds":{"left":0.28856382,"top":0.36073422,"width":0.014295213,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"bounds":{"left":0.30418882,"top":0.35993615,"width":0.0023271276,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"offline_access","depth":24,"bounds":{"left":0.30784574,"top":0.36073422,"width":0.032912236,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":23,"bounds":{"left":0.34208778,"top":0.35993615,"width":0.0013297872,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXHeading","text":"Key Security Notes","depth":22,"bounds":{"left":0.13264628,"top":0.3926576,"width":0.41855052,"height":0.018355945},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"Key Security Notes","depth":23,"bounds":{"left":0.13264628,"top":0.39505187,"width":0.046875,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"PKCE","depth":25,"bounds":{"left":0.14128989,"top":0.424581,"width":0.011635638,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"prevents authorization code interception","depth":24,"bounds":{"left":0.15259309,"top":0.424581,"width":0.0831117,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"State parameter","depth":25,"bounds":{"left":0.14128989,"top":0.4405427,"width":0.034906916,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"provides CSRF protection","depth":24,"bounds":{"left":0.17619681,"top":0.4405427,"width":0.052526597,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"No server-side token storage","depth":25,"bounds":{"left":0.14128989,"top":0.4557063,"width":0.0625,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"— validation is stateless via JWKS","depth":24,"bounds":{"left":0.20345744,"top":0.4557063,"width":0.06948138,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"The README/API docs mention an","depth":24,"bounds":{"left":0.14128989,"top":0.471668,"width":0.07014628,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"X-API-Key","depth":25,"bounds":{"left":0.21243352,"top":0.47246608,"width":0.021276595,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"header, but that's","depth":24,"bounds":{"left":0.23503989,"top":0.471668,"width":0.038231384,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"outdated","depth":25,"bounds":{"left":0.27293882,"top":0.471668,"width":0.019614361,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"— the actual code uses Bearer tokens only","depth":24,"bounds":{"left":0.2925532,"top":0.471668,"width":0.08676862,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Config lives in","depth":24,"bounds":{"left":0.13264628,"top":0.50438946,"width":0.03025266,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":23,"bounds":{"left":0.16256648,"top":0.50438946,"width":0.0013297872,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXLink","text":"docker-compose.yml","depth":23,"bounds":{"left":0.16389628,"top":0.50438946,"width":0.042220745,"height":0.011971269},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"docker-compose.yml","depth":24,"bounds":{"left":0.16389628,"top":0.50438946,"width":0.042220745,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"(lines 30–33):","depth":23,"bounds":{"left":0.20611702,"top":0.50438946,"width":0.030585106,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"HYDRA_PUBLIC_URL","depth":24,"bounds":{"left":0.23803191,"top":0.5051876,"width":0.037898935,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"bounds":{"left":0.2769282,"top":0.50438946,"width":0.0026595744,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"REQUIRED_SCOPES","depth":24,"bounds":{"left":0.28091756,"top":0.5051876,"width":0.03523936,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":",","depth":23,"bounds":{"left":0.31715426,"top":0.50438946,"width":0.0026595744,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"OAUTH_CLIENT_ID","depth":24,"bounds":{"left":0.32114363,"top":0.5051876,"width":0.03523936,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":", and optionally","depth":23,"bounds":{"left":0.35738033,"top":0.50438946,"width":0.032247342,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"TOKEN_AUDIENCE","depth":24,"bounds":{"left":0.390625,"top":0.5051876,"width":0.032912236,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/","depth":23,"bounds":{"left":0.42486703,"top":0.50438946,"width":0.003656915,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"JWKS_CACHE_TTL","depth":24,"bounds":{"left":0.42985374,"top":0.5051876,"width":0.032912236,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":23,"bounds":{"left":0.4637633,"top":0.50438946,"width":0.0016622341,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Message actions","depth":24,"bounds":{"left":0.54787236,"top":0.52992815,"width":0.0066489363,"height":0.015961692},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"compare it to location logger and make it the same. Lets remove hydra and only use authentic (or whatever location logger uses).","depth":25,"bounds":{"left":0.125,"top":0.5434956,"width":0.25930852,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Thinking","depth":23,"bounds":{"left":0.13264628,"top":0.5818037,"width":0.023936171,"height":0.015961692},"on_screen":true,"role_description":"disclosure triangle","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Thinking","depth":24,"bounds":{"left":0.13264628,"top":0.58339983,"width":0.017287234,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Let me first explore how location-logger handles authorization.","depth":23,"bounds":{"left":0.13264628,"top":0.6113328,"width":0.12599733,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Agent:","depth":25,"bounds":{"left":0.13264628,"top":0.64166003,"width":0.014295213,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Explore location-logger authorization code","depth":25,"bounds":{"left":0.14793883,"top":0.6432562,"width":0.0930851,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":24,"bounds":{"left":0.13530585,"top":0.6703911,"width":0.004654255,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Explore the location-logger project at /volume2/docker/location-logger and give me a thorough understanding of how authorization works. I need to know:\n\n1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)\n2. Where auth is enforced (middleware, decorators, route guards, etc.)\n3. What roles/permissions exist\n4. How tokens are issued, validated, and refreshed\n5. Any auth-related config (env vars, settings)\n6. Frontend auth flow (how the user logs in, how tokens are stored and used)\n7. The full directory/file structure of the project\n\nPlease read ALL relevant source files completely and give me a detailed summary with file paths and line numbers. I need enough detail to replicate this auth system in another project.","depth":25,"bounds":{"left":0.14361702,"top":0.6703911,"width":0.40492022,"height":0.05027933},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Bash","depth":24,"bounds":{"left":0.13264628,"top":0.74461293,"width":0.010638298,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"IN","depth":25,"bounds":{"left":0.13530585,"top":0.7725459,"width":0.004654255,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"find /volume2/docker/location-logger -type f -name \"*.py\" -o -name \"*.js\" -o -name \"*.ts\" -o -name \"*.json\" | head -50","depth":25,"bounds":{"left":0.14594415,"top":0.7725459,"width":0.2613032,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Copy code to clipboard","depth":24,"bounds":{"left":0.5418883,"top":0.76855546,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"Copy code","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"OUT","depth":24,"bounds":{"left":0.13530585,"top":0.79888266,"width":0.0066489363,"height":0.011173184},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"/volume2/docker/location-logger/.mcp.json\n/volume2/docker/location-logger/app/__init__.py\n/volume2/docker/location-logger/app/utils.py\n/volume2/docker/location-logger/app/config.py\n/volume2/docker/location-logger/app/main.py\n/volume2/docker/location-logger/alembic/env.py\n/volume2/docker/location-logger/mcp-server/server.py\n/volume2/docker/location-logger/alembic/versions/20260203_201500_create_location_points_table.py\n/volume2/docker/location-logger/alembic/versions/20260209_200000_add_enhanced_fields_and_tables.py\n/volume2/docker/location-logger/alembic/versions/20260214_000000_add_users_and_audit_logs.py\n/volume2/docker/location-logger/alembic/versions/20260320_000000_add_audit_query_string.py\n/volume2/docker/location-logger/app/services/geofence_service.py\n/volume2/docker/location-logger/app/services/recorder_service.py\n/volume2/docker/location-logger/app/services/trip_service.py\n/volume2/docker/location-logger/app/services/enhanced_location_service.py\n/volume2/docker/location-logger/app/services/geocoding_service.py\n/volume2/docker/location-logger/app/clients/__init__.py\n/volume2/docker/location-logger/app/clients/dawarich_client.py\n/volume2/docker/location-logger/app/clients/owntracks_client.py\n/volume2/docker/location-logger/app/models/areas.py\n/volume2/docker/location-logger/app/models/geofence_events.py\n/volume2/docker/location-logger/app/models/trips.py\n/volume2/docker/location-logger/app/models/user.py\n/volume2/docker/location-logger/app/models/location.py\n/volume2/docker/location-logger/app/models/audit_log.py\n/volume2/docker/location-logger/app/auth/__init__.py\n/volume2/docker/location-logger/app/auth/dependencies.py\n/volume2/docker/location-logger/app/auth/utils.py\n/volume2/docker/location-logger/app/api/enhanced_endpoints.py\n/volume2/docker/location-logger/app/db/base.py\n/volume2/docker/location-logger/app/db/session.py","depth":25,"bounds":{"left":0.14594415,"top":0.79888266,"width":0.21708776,"height":0.05027933},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"✽","depth":22,"bounds":{"left":0.125,"top":0.8707103,"width":0.0033244682,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Sussing...","depth":22,"bounds":{"left":0.13198139,"top":0.87230647,"width":0.020279255,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"Queue another message…","depth":24,"bounds":{"left":0.22406915,"top":0.9082203,"width":0.22573139,"height":0.0311253},"on_screen":true,"value":"Queue another message…","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Queue another message…","depth":26,"bounds":{"left":0.2287234,"top":0.91779727,"width":0.05285904,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Add","depth":24,"bounds":{"left":0.22573139,"top":0.94413406,"width":0.008643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show command menu (/)","depth":23,"bounds":{"left":0.23503989,"top":0.94413406,"width":0.008643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"docker-compose.yml","depth":23,"bounds":{"left":0.24800532,"top":0.94413406,"width":0.048204787,"height":0.0207502},"on_screen":true,"help_text":"Showing Claude your current file selection (docker-compose.yml)","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"docker-compose.yml","depth":24,"bounds":{"left":0.25664893,"top":0.9489226,"width":0.036901597,"height":0.0103751},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Edit automatically","depth":24,"bounds":{"left":0.3956117,"top":0.94413406,"width":0.04255319,"height":0.0207502},"on_screen":true,"help_text":"Claude will edit your selected text or the whole file. Click to change, or press Shift+Tab to cycle.","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Edit automatically","depth":25,"bounds":{"left":0.40425533,"top":0.9489226,"width":0.03125,"height":0.0103751},"on_screen":true,"role_description":"text"}]...
|
8940274766898061173
|
-114494451588214198
|
visual_change
|
accessibility
|
NULL
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 55 pending changes
55
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: docker [SSH: nas]
Explorer Section: docker [SSH: nas]
DOCKER [SSH: NAS]
dawarich
dsk-uploader
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
alembic
app
mcp-server
.env
.env.example
.gitignore
.mcp.json
M
alembic.ini
docker-compose.yml
M
Dockerfile
README.md
M
requirements.txt
M
today_map.html
mariadb
meeting-detector
mindfulmama
n8n
notifier-app
npm
oauth
obsidian
ollama
open-webui
openttd
openvpn-client
orchestrator
outfit-app
owntracks-stack
paperlessngx
payments-logger
.claude
backend
frontend
.env
.env.example
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
Review payment logger au…
docker-compose.yml, preview, Editor Group 2
docker-compose.yml, preview, Editor Group 2
remote SSH: nas
SSH: nas
location-logger (Git)
location-logger
location-logger (Git) - main*, Checkout Branch/Tag...
main*
location-logger (Git) - Synchronize Changes
No Problems
0
0
No Ports Forwarded
0
Notifications
Sign In
Sign In
Compose
Editor Language Status: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 1, Col 1
Screen Reader Optimized
expanded
Clear
Review payment logger authorization implementation
Session history
New session
Message actions
review payment_logger project how toes authorization work
Thinking
Thinking
Let me explore the payment_logger project structure first.
Bash
Find payment_logger project files
IN
find /volume2/docker -type f -name "*.py" -o -name "*.yml" -o -name "*.yaml" -o -name "*.env" -o -name "*.toml" -o -name "*.cfg" | grep -i payment | head -50
Copy code to clipboard
OUT
bfs: error: /volume2/docker/portnotedb/pg_commit_ts: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_dynshmem: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_notify: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_serial: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_snapshots: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_subtrans: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_twophase: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_multixact: Permission denied.
bfs: error: /volume2/docker/portnotedb/base: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_replslot: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_tblspc: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_stat: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_stat_tmp: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_xact: Permission denied.
bfs: error: /volume2/docker/portnotedb/pg_logical: Permission denied.
bfs: error: /volume2/docker/openttd/.dbus: Permission denied.
bfs: error: /volume2/docker/immich/db: Permission denied.
bfs: error: /volume2/docker/paperlessngx/db: Permission denied.
bfs: error: /volume2/docker/appflowy/postgres: Permission denied.
bfs: error: /volume2/docker/auth/db: Permission denied.
bfs: error: /volume2/docker/openttd/.XDG/dbus-1: Permission denied.
bfs: error: /volume2/docker/openttd/.config/pulse: Permission denied.
bfs: error: /volume2/docker/openttd/.cache/openbox/sessions: Permission denied.
/volume2/docker/payments-logger/.env
/volume2/docker/payments-logger/docker-compose.yml
/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml
Bash
List docker directory contents
IN
ls /volume2/docker/
Copy code to clipboard
OUT
adguard
ai-stack
app-db
appflowy
audiobookshelf
auth
beszel
bitwarden
dawarich
dsk-uploader
flask-app
garmin-connector
gitea
health
health-tracker
homarr
hst
immich
jellyfinht
kavita
libreoffice
linkwarden
location-logger
mariadb
meeting-detector
mindfulmama
n8n
notifier-app
npm
oauth
obsidian
ollama
openttd
openvpn-client
open-webui
orchestrator
outfit-app
owntracks-stack
paperlessngx
payments-logger
personal-log
personal-log-system
player
portainer
portnotedb
reminders-app
romm
second-brain
static
stirling
syncthing
tailscale
temp-logger
test-app
tmp
torrent-stack
tree
trilium
tubearchivist
twenty
uptimekuma
ytptube
Agent:
Explore payment_logger authorization code
IN
Explore the payments-logger project at /volume2/docker/payments-logger and give me a thorough understanding of how authorization works. I need to know:
1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)
2. Where auth is enforced (middleware, decorators, route guards, etc.)
3. What roles/permissions exist
4. How tokens are issued, validated, and refreshed
5. Any auth-related config (env vars, settings)
Please read the key source files and give me a clear summary with file paths and line numbers for the important parts.
Bash
IN
find /volume2/docker/payments-logger -type f -name "*.py" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" | grep -E "(auth|token|jwt|session|permission|role)" | head -20
Copy code to clipboard
OUT
(Bash completed with no output)
Bash
IN
ls -la /volume2/docker/payments-logger/ 2>/dev/null || echo "Directory not found"
Copy code to clipboard
OUT
total 36
drwxrwxrwx 1 Adm1n admin 162 Mar 29 18:01 .
drwxrwxrwx 1 root root 1146 Apr 26 18:49 ..
-rwxrwxrwx 1 Adm1n admin 15233 Feb 22 17:08 API.md
drwxrwxrwx 1 Adm1n admin 88 Mar 29 15:41 backend
drwxrwxrwx 1 Adm1n admin 38 Mar 29 17:07 .claude
-rwxrwxrwx 1 Adm1n admin 1502 Mar 29 18:01 docker-compose.yml
-rwxrwxrwx 1 Adm1n admin 1066 Mar 29 15:44 .env
-rwxrwxrwx 1 Adm1n admin 1078 Feb 14 17:53 .env.example
drwxrwxrwx 1 Adm1n admin 194 Mar 29 15:44 frontend
-rwxrwxrwx 1 Adm1n admin 19 Feb 14 17:54 .gitignore
-rwxrwxrwx 1 Adm1n admin 4052 Feb 14 19:06 README.md
Bash
IN
find /volume2/docker/payments-logger/backend -type f | head -30
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/Dockerfile
/volume2/docker/payments-logger/backend/.dockerignore
/volume2/docker/payments-logger/backend/package.json
/volume2/docker/payments-logger/backend/prisma/schema.prisma
/volume2/docker/payments-logger/backend/src/parser.js
/volume2/docker/payments-logger/backend/src/index.js
/volume2/docker/payments-logger/backend/src/auth.js
/volume2/docker/payments-logger/backend/prisma/migrations/migration_lock.toml
/volume2/docker/payments-logger/backend/src/routes/payments.js
/volume2/docker/payments-logger/backend/prisma/migrations/20260210_init/migration.sql
/volume2/docker/payments-logger/backend/prisma/migrations/20260210_simplify/migration.sql
/volume2/docker/payments-logger/backend/prisma/migrations/20260222_add_card/migration.sql
Bash
IN
find /volume2/docker/payments-logger -type f \( -name "*.js" -o -name "*.json" -o -name "*.md" \) | sort
Copy code to clipboard
OUT
/volume2/docker/payments-logger/API.md
/volume2/docker/payments-logger/backend/package.json
/volume2/docker/payments-logger/backend/src/auth.js
/volume2/docker/payments-logger/backend/src/index.js
/volume2/docker/payments-logger/backend/src/parser.js
/volume2/docker/payments-logger/backend/src/routes/payments.js
/volume2/docker/payments-logger/.claude/settings.local.json
/volume2/docker/payments-logger/frontend/package.json
/volume2/docker/payments-logger/frontend/postcss.config.js
/volume2/docker/payments-logger/frontend/src/auth.js
/volume2/docker/payments-logger/frontend/tailwind.config.js
/volume2/docker/payments-logger/frontend/vite.config.js
/volume2/docker/payments-logger/README.md
Read
.env
.env
Read
auth.js
auth.js
Read
index.js
index.js
Read
package.json
package.json
Read
auth.js
auth.js
Read
API.md
API.md
Read
payments.js
payments.js
Read
.env.example
.env.example
Read
schema.prisma
schema.prisma
Bash
IN
grep -r "API_KEY\|auth\|token\|oauth\|hydra" /volume2/docker/payments-logger/backend/src --include="*.js" -n
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/src/index.js:6:const { bearerTokenMiddleware, wellKnownRouter } = require('./auth');
/volume2/docker/payments-logger/backend/src/index.js:18:// ── OAuth Bearer token auth (skips public paths) ──────────────────────────────
/volume2/docker/payments-logger/backend/src/auth.js:2: * OAuth 2.1 / Hydra JWT authentication for Express.
/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints
/volume2/docker/payments-logger/backend/src/auth.js:33:function sendUnauthorized(res, detail, error = 'invalid_token') {
/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata="${realm}/.well-known/oauth-protected-resource"`,
/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server="${HYDRA_PUBLIC_URL}"`,
/volume2/docker/payments-logger/backend/src/auth.js:52: * On success, attaches the decoded payload to req.tokenPayload.
/volume2/docker/payments-logger/backend/src/auth.js:54: * @param {Set<string>} publicPaths Paths to skip auth for.
/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',
/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',
/volume2/docker/payments-logger/backend/src/auth.js:66: const auth = req.headers.authorization || '';
/volume2/docker/payments-logger/backend/src/auth.js:67: if (!auth.toLowerCase().startsWith('bearer ')) {
/volume2/docker/payments-logger/backend/src/auth.js:68: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');
/volume2/docker/payments-logger/backend/src/auth.js:71: const token = auth.slice(7).trim();
/volume2/docker/payments-logger/backend/src/auth.js:72: if (!token) {
/volume2/docker/payments-logger/backend/src/auth.js:73: return sendUnauthorized(res, 'Bearer token required', 'invalid_request');
/volume2/docker/payments-logger/backend/src/auth.js:82: const { payload } = await jwtVerify(token, getJwks(), verifyOptions);
/volume2/docker/payments-logger/backend/src/auth.js:83: req.tokenPayload = payload;
/volume2/docker/payments-logger/backend/src/auth.js:87: const error = msg.includes('expired') ? 'invalid_token' : 'invalid_token';
/volume2/docker/payments-logger/backend/src/auth.js:88: sendUnauthorized(res, msg, error);
/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],
/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:119: authorization_endpoint: doc.authorization_endpoint,
/volume2/docker/payments-logger/backend/src/auth.js:120: token_endpoint: doc.token_endpoint,
/volume2/docker/payments-logger/backend/src/auth.js:123: grant_types_supported: doc.grant_types_supported || ['authorization_code', 'refresh_token'],
/volume2/docker/payments-logger/backend/src/auth.js:124: [API_KEY]: doc.[API_KEY] || ['none', 'client_secret_basic'],
/volume2/docker/payments-logger/backend/src/auth.js:128: id_token_signing_alg_values_supported: doc.id_token_signing_alg_values_supported || ['RS256'],
/volume2/docker/payments-logger/backend/src/routes/payments.js:58:// ── Ingest a payment (public — no auth) ──────────────────────────────────────
Bash
IN
find /volume2/docker/payments-logger/frontend/src -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | head -20
Copy code to clipboard
OUT
/volume2/docker/payments-logger/frontend/src/main.jsx
/volume2/docker/payments-logger/frontend/src/App.jsx
/volume2/docker/payments-logger/frontend/src/auth.js
/volume2/docker/payments-logger/frontend/src/components/PaymentCard.jsx
/volume2/docker/payments-logger/frontend/src/components/PaymentList.jsx
/volume2/docker/payments-logger/frontend/src/components/FilterBar.jsx
/volume2/docker/payments-logger/frontend/src/components/PaymentTable.jsx
/volume2/docker/payments-logger/frontend/src/components/AuthProvider.jsx
/volume2/docker/payments-logger/frontend/src/components/AuthCallback.jsx
Read
AuthProvider.jsx
AuthProvider.jsx
Read
AuthCallback.jsx
AuthCallback.jsx
Read
App.jsx
App.jsx
Read
main.jsx
main.jsx
Read
vite.config.js
vite.config.js
Bash
IN
grep -r "HYDRA\|VITE" /volume2/docker/payments-logger --include=".env*" -n
Copy code to clipboard
OUT
(Bash completed with no output)
Read
docker-compose.yml
docker-compose.yml
Bash
IN
grep -r "API_KEY" /volume2/docker/payments-logger/backend --include="*.js" -n
Copy code to clipboard
OUT
(Bash completed with no output)
Read
README.md
README.md
Bash
IN
grep -n "HYDRA\|oauth\|TOKEN" /volume2/docker/payments-logger/backend/src/index.js /volume2/docker/payments-logger/backend/src/auth.js | head -40
Copy code to clipboard
OUT
/volume2/docker/payments-logger/backend/src/auth.js:6: * - wellKnownRouter — serves /.well-known/oauth-* discovery endpoints
/volume2/docker/payments-logger/backend/src/auth.js:11:const HYDRA_PUBLIC_URL = (process.env.HYDRA_PUBLIC_URL || '').replace(/\/$/, '');
/volume2/docker/payments-logger/backend/src/auth.js:24: new URL(`${HYDRA_PUBLIC_URL}/.well-known/jwks.json`)
/volume2/docker/payments-logger/backend/src/auth.js:39: `resource_metadata="${realm}/.well-known/oauth-protected-resource"`,
/volume2/docker/payments-logger/backend/src/auth.js:40: `authorization_server="${HYDRA_PUBLIC_URL}"`,
/volume2/docker/payments-logger/backend/src/auth.js:58: '/.well-known/oauth-protected-resource',
/volume2/docker/payments-logger/backend/src/auth.js:59: '/.well-known/oauth-authorization-server',
/volume2/docker/payments-logger/backend/src/auth.js:78: issuer: HYDRA_PUBLIC_URL,
/volume2/docker/payments-logger/backend/src/auth.js:81: if (process.env.TOKEN_AUDIENCE) verifyOptions.audience = process.env.TOKEN_AUDIENCE;
/volume2/docker/payments-logger/backend/src/auth.js:98:wellKnownRouter.get('/.well-known/oauth-protected-resource', (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:103: authorization_servers: [HYDRA_PUBLIC_URL],
/volume2/docker/payments-logger/backend/src/auth.js:107: jwks_uri: `${HYDRA_PUBLIC_URL}/.well-known/jwks.json`,
/volume2/docker/payments-logger/backend/src/auth.js:111:wellKnownRouter.get('/.well-known/oauth-authorization-server', async (_req, res) => {
/volume2/docker/payments-logger/backend/src/auth.js:113: const r = await fetch(`${HYDRA_PUBLIC_URL}/.well-known/openid-configuration`);
/volume2/docker/payments-logger/backend/src/auth.js:118: issuer: doc.issuer || HYDRA_PUBLIC_URL,
Thinking
Thinking
payments-logger Authorization Overview
payments-logger Authorization Overview
The project uses
OAuth 2.1 with PKCE
via an external Hydra OAuth server at
hydra.lakylak.xyz
.
Auth Flow
Auth Flow
Frontend
(
frontend/src/auth.js
frontend/src/auth.js
):
Fetches OAuth server metadata via discovery endpoint
Generates a PKCE code verifier/challenge (SHA-256)
Redirects user to Hydra login with
code_challenge
On callback, exchanges code + verifier for tokens
Stores
access_token
,
refresh_token
in
localStorage
(
pl_oauth_tokens
)
Auto-refreshes token 30 seconds before expiry
Backend
(
backend/src/auth.js
backend/src/auth.js
):
bearerTokenMiddleware
validates the
Authorization: Bearer <token>
header
Fetches Hydra's JWKS (cached 300s) and verifies JWT signature (RS256/ES256/PS256)
Validates issuer (
https://hydra.lakylak.xyz
) and optional audience
Enforcement
Enforcement
Applied globally in
backend/src/index.js:19
backend/src/index.js:19
with these
public (unprotected) paths
:
/.well-known/oauth-protected-resource
/.well-known/oauth-authorization-server
/api/health
/api/payments/ingest
(rate-limited at 200 req/min — for SMS ingestion)
Roles/Permissions
Roles/Permissions
No RBAC
— it's binary: authenticated vs. unauthenticated. Required scopes:
openid
,
offline_access
.
Key Security Notes
Key Security Notes
PKCE
prevents authorization code interception
State parameter
provides CSRF protection
No server-side token storage
— validation is stateless via JWKS
The README/API docs mention an
X-API-Key
header, but that's
outdated
— the actual code uses Bearer tokens only
Config lives in
docker-compose.yml
docker-compose.yml
(lines 30–33):
HYDRA_PUBLIC_URL
,
REQUIRED_SCOPES
,
OAUTH_CLIENT_ID
, and optionally
TOKEN_AUDIENCE
/
JWKS_CACHE_TTL
.
Message actions
compare it to location logger and make it the same. Lets remove hydra and only use authentic (or whatever location logger uses).
Thinking
Thinking
Let me first explore how location-logger handles authorization.
Agent:
Explore location-logger authorization code
IN
Explore the location-logger project at /volume2/docker/location-logger and give me a thorough understanding of how authorization works. I need to know:
1. What auth mechanism is used (JWT, sessions, API keys, OAuth, etc.)
2. Where auth is enforced (middleware, decorators, route guards, etc.)
3. What roles/permissions exist
4. How tokens are issued, validated, and refreshed
5. Any auth-related config (env vars, settings)
6. Frontend auth flow (how the user logs in, how tokens are stored and used)
7. The full directory/file structure of the project
Please read ALL relevant source files completely and give me a detailed summary with file paths and line numbers. I need enough detail to replicate this auth system in another project.
Bash
IN
find /volume2/docker/location-logger -type f -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.json" | head -50
Copy code to clipboard
OUT
/volume2/docker/location-logger/.mcp.json
/volume2/docker/location-logger/app/__init__.py
/volume2/docker/location-logger/app/utils.py
/volume2/docker/location-logger/app/config.py
/volume2/docker/location-logger/app/main.py
/volume2/docker/location-logger/alembic/env.py
/volume2/docker/location-logger/mcp-server/server.py
/volume2/docker/location-logger/alembic/versions/20260203_201500_create_location_points_table.py
/volume2/docker/location-logger/alembic/versions/20260209_200000_add_enhanced_fields_and_tables.py
/volume2/docker/location-logger/alembic/versions/20260214_000000_add_users_and_audit_logs.py
/volume2/docker/location-logger/alembic/versions/20260320_000000_add_audit_query_string.py
/volume2/docker/location-logger/app/services/geofence_service.py
/volume2/docker/location-logger/app/services/recorder_service.py
/volume2/docker/location-logger/app/services/trip_service.py
/volume2/docker/location-logger/app/services/enhanced_location_service.py
/volume2/docker/location-logger/app/services/geocoding_service.py
/volume2/docker/location-logger/app/clients/__init__.py
/volume2/docker/location-logger/app/clients/dawarich_client.py
/volume2/docker/location-logger/app/clients/owntracks_client.py
/volume2/docker/location-logger/app/models/areas.py
/volume2/docker/location-logger/app/models/geofence_events.py
/volume2/docker/location-logger/app/models/trips.py
/volume2/docker/location-logger/app/models/user.py
/volume2/docker/location-logger/app/models/location.py
/volume2/docker/location-logger/app/models/audit_log.py
/volume2/docker/location-logger/app/auth/__init__.py
/volume2/docker/location-logger/app/auth/dependencies.py
/volume2/docker/location-logger/app/auth/utils.py
/volume2/docker/location-logger/app/api/enhanced_endpoints.py
/volume2/docker/location-logger/app/db/base.py
/volume2/docker/location-logger/app/db/session.py
✽
Sussing...
Queue another message…
Queue another message…
Add
Show command menu (/)
docker-compose.yml
docker-compose.yml
Edit automatically
Edit automatically...
|
10510
|
NULL
|
NULL
|
NULL
|
|
2422
|
104
|
23
|
2026-05-07T11:16:24.679008+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778152584679_m2.jpg...
|
Claude
|
Claude
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Open sidebar
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Definition of incarcerated
More options for Definition of incarcerated
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Salesforce API errors with Organization and FieldDefinition queries
More options for Salesforce API errors with Organization and FieldDefinition queries
Daily activity summary from screenpipe data
More options for Daily activity summary from screenpipe data
MacBook unexpected restarts and kanji screen
More options for MacBook unexpected restarts and kanji screen
Security patch review and testing guidance
More options for Security patch review and testing guidance
Food calorie values reference
More options for Food calorie values reference
Tracking location history from last week
More options for Tracking location history from last week
Lukas Pro
Get apps and extensions
HubSpot rate limit implementation strategy, rename chat
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Open sidebar
Claude is responding
You said: So just a solution for rate limit implementation.
You said: So just a solution for rate limit implementation.
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
updating activity (engagement, meeting, call, note)
searching for a company or contact
updating deal
patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
where to store it
how to read it
how to work with multiple jobs
how to work with paginated requests Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? [API usage guidelines and limits - HubSpot docs](
https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
). Example of such a error Client error:
POST https://api.hubapi.com/crm/v3/objects/contact/search
resulted in a
429 Too Many Requests
response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019dffc4-4 (truncated...). Think carefully about the implementation and potential issue and bottlenecks.
Show more
10:42
Edit
Copy
Claude responded: Current HubSpot limits (Apr 2026)
Claude responded: Current HubSpot limits (Apr 2026)
Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms
Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms
Current HubSpot limits (Apr 2026)
Current HubSpot limits (Apr 2026)
For privately distributed apps (which is what Jiminny presumably uses with HubSpot), the limits are layered: a per-app burst window of 100/10s on Free/Starter, 190/10s on Pro/Enterprise, or 250/10s with the API Limit Increase add-on
, plus a per-account daily cap of 250k / 625k / 1M depending on tier — shared across all private apps in the account
. The burst window is per app; the daily quota is per portal.
hubspot
hubspot
hubspot
hubspot
The Search API is special: five requests per second per account, capped at 200 records per page, with a hard 10,000-result ceiling per query
. That's the limit the 429 in your example is hitting (
secondly limit
on
/crm/v3/objects/contact/search
). It's a separate bucket from the 190/10s, and critically, search responses do not include the
X-HubSpot-RateLimit-*
headers
— you can't read remaining capacity off the response, you have to track it yourself.
hubspot
hubspot
hubspot
hubspot
Non-search endpoints return
X-HubSpot-RateLimit-Remaining
,
-Max
, and
-Interval-Milliseconds
. The daily headers are not returned for OAuth-authenticated calls
, so for those you check the account-info endpoint or maintain your own counter. There's also a soft rule: errors should stay under 5% of total daily requests
— that's enforced for marketplace listing, but it's also a sane operational target.
hubspot
hubspot
hubspot
hubspot
So you have
four buckets
to track at minimum: app-burst (10s sliding), search-secondly (1s sliding), account-daily (24h fixed, midnight in HubSpot's TZ), and per-app daily share (your own budgeting on top of the account cap).
Where to store it
Where to store it
Redis. It's the only realistic option once you have multiple queue workers — DB-backed counters serialize too much, and in-process state can't coordinate across workers. Laravel already speaks Redis natively, and you get atomic
INCR
/
EXPIRE
plus Lua scripting for true CAS semantics.
Key layout I'd use:
Copy to clipboard
hubspot:rl:burst:{portalId}:{appId} ZSET (sliding 10s)
hubspot:rl:search:{portalId} ZSET (sliding 1s)
hubspot:rl:daily:{portalId} STRING + TTL to midnight TZ
hubspot:rl:meta:{portalId} HASH (last seen headers)
The two short-window buckets are sliding-window-log (Redis ZSET, score =
microtime
, member = unique request id). Lua script removes entries older than the window, counts remaining slots, and only adds the new one if there's room — all atomic. Fixed windows are simpler but allow 2× the limit at the boundary, which on a 5/s window is brutal.
The daily bucket is a plain
INCR
with TTL set to seconds-until-midnight in
HubSpot's account timezone
, not yours — Sofia is UTC+2/+3 but your portal might be set to US Eastern. Get this once and cache it.
How to read it
How to read it
Two-phase: optimistic pre-check, authoritative post-update.
Before a call, run the Lua acquire script. If it returns "denied," sleep until a slot frees up (the script can return ms-until-next-slot) or push the job back to the queue with a delay. Don't busy-loop.
After the call, parse
X-HubSpot-RateLimit-Remaining
/
-Max
/
-Interval-Milliseconds
and store them in
hubspot:rl:meta:{portalId}
. This is your reality check — if your local counter says 50 remaining but HubSpot's header says 5, you trust HubSpot and clamp your counter. This handles clock drift, missed accounting (e.g., crashed worker that took a token but never made the request), and other apps in the same account consuming the daily budget invisibly.
For search specifically, since headers don't come back, the local counter
is
the source of truth — meaning if you ever crash mid-flight, you've under-counted. Always release tokens on connection-level failure but
not
on 429 (you really did make that request).
How to work with multiple jobs
How to work with multiple jobs
The queue layer needs to enforce concurrency separately from the rate limiter. Both work together:
Concurrency cap
via
Queue::throttle()
or a Redis semaphore — limits how many sync workers run in parallel against HubSpot. Without this, you can have 50 workers all blocked waiting for tokens, which is wasteful and creates retry storms.
Per-tenant fairness
— if you sync many Jiminny customers into different HubSpot portals, each portal has its own bucket but you still want one slow portal not to starve others. Either separate queues per portal or a fair-share scheduler.
Priority lanes
— webhook-driven updates (user-visible latency) should outrank background batch syncs. Two queues:
hubspot-priority
and
hubspot-bulk
, with priority workers taking 70% of the burst budget and bulk taking 30% (enforce via separate sub-buckets if you really want hard separation, otherwise just pull from priority first).
Backoff on 429
— respect the
Retry-After
header. If absent, exponential with jitter (e.g.,
min(2^attempt * 250ms, 30s) ± 20%
). The jitter is non-negotiable; without it, simultaneous 429s retry in lockstep and you get thundering-herd 429s on retry.
How to work with paginated requests
How to work with paginated requests
The trap is treating "fetch all" as one logical operation. Each page is its own API call and competes for tokens with everything else.
Two practical rules:
Don't hold the worker idle while paginating.
If page N takes 500ms and page N+1 needs to wait 800ms for a token, you've burned 800ms of worker time doing nothing. Instead, fetch page N, dispatch the
processing
of page N as a separate job, and queue a "fetch page N+1" job with a delay equal to the wait time. Each page becomes its own atomic unit.
Always batch where HubSpot offers it.
POST /crm/v3/objects/{type}/batch/{read|update|create}
accepts up to 100 IDs per call. For your "patching IDs" flow this is the difference between 600 calls and 6.
Walkthrough: 600 opportunities
Walkthrough: 600 opportunities
Naive flow (one PATCH per deal): 600 calls. At 190/10s that's ~32s of API time, well under daily. You'd hit burst easily without throttling. Plus likely 1-3 search calls upfront and N association calls.
Sane flow:
Identify deltas
— if you can avoid Search at all, do. Use
lastmodifieddate
filter on a single search if you must, paginate at limit=200. For 600 deals that's 3 search calls @ 5/s = ~600ms naturally throttled.
Batch read
current state —
POST /crm/v3/objects/deals/batch/read
with 100 IDs per call → 6 calls. Burst weight 6.
Compute diff
in your code (no API).
Batch update
—
POST /crm/v3/objects/deals/batch/update
with 100 per call → 6 calls.
Associations
—
POST /crm/v4/associations/{from}/{to}/batch/create
if needed → another few batch calls.
Total: ~15-20 API calls for 600 deals. Burst budget consumed: 20/190 over ~5s. Daily impact: trivial.
Recognition: pre-check denies if
<weight>
slots aren't free; on 429 the response surfaces
policyName
(DAILY/SECONDLY) — log it, push the job back with
Retry-After
delay, increment a metric so you can alarm on sustained 429 rate.
Pitfalls & bottlenecks worth pre-empting
Pitfalls & bottlenecks worth pre-empting
Daily quota is shared across the whole portal
, including any n8n flows, other private apps, marketing automation calling APIs, etc. Budget for ~70-80% of the documented limit, not 100%. Add an internal per-app daily cap so a runaway sync can't starve other apps.
Search is uniquely fragile
: separate 5/s bucket, no headers to read, 10k-result cap per query. If you ever hit 10k+ deals matching a filter, partition by
hs_object_id
ranges or by
lastmodifieddate
windows.
Retry storms after HubSpot incidents
— always jitter, always cap total retries (e.g., 5 attempts, then dead-letter).
Webhook → sync → webhook loops
— when your update fires a HubSpot webhook that triggers another sync. Tag your own writes (e.g., a custom property
jy_last_sync_id
) and ignore webhooks where it just changed.
Token refresh
— OAuth refresh itself counts against burst. Cache the access token in Redis with TTL =
expires_in - 60s
, single-flight the refresh (only one worker refreshes; others wait on a lock).
Search latency for fresh writes
— newly created/updated objects don't appear in search instantly. Don't write-then-search; use returned IDs directly.
Clock skew
— Redis is authoritative. Don't use
time()
from each worker for window scoring; use
redis.call('TIME')
inside the Lua script.
Dead workers with held tokens
— sliding-window-log handles this naturally because tokens expire by score; token-bucket implementations need explicit release-on-crash logic.
The 5% error rule
counts every 429 toward your error budget, even successfully retried ones. If your pre-check is working well, 429s should be near zero — if they're not, your pre-check is wrong, not just slow.
Phantom contention with Laravel
RateLimited
middleware
: it's per-job-class by default, not per-portal. If your
SyncDealJob
and
UpdateActivityJob
are different classes both calling HubSpot, they don't share a Laravel-level limiter — you need a single
HubspotRateLimiter
service that everything routes through.
If you want, I can sketch the Lua script for the sliding-window acquire and the Laravel service wrapper around it.
Copy
Give positive feedback
Give negative feedback
You said: What will happen if there are two different types of rate limits?
You said: What will happen if there are two different types of rate limits?
What will happen if there are two different types of rate limits?
Search requests that have 5 requests per second
The other rate limit that has, let's say, 100 requests per 10 seconds How do these work together? Is there some conflict? Can I reach the limit with one request and block either as well, or are these separated? Can I still continue making search requests once I hit the normal requests limits?
13:40
Edit
Copy
Claude responded: They're independent buckets — with one shared bucket on top.
Claude responded: They're independent buckets — with one shared bucket on top.
Analyzed independent rate limit buckets and their practical interactions
Analyzed independent rate limit buckets and their practical interactions
They're independent buckets — with one shared bucket on top.
The three buckets and what each call hits
The three buckets and what each call hits
Copy to clipboard
┌──────────────────┐
Search request ──→ │ Search: 5/sec │ ──┐
└──────────────────┘ │
├──→ ┌─────────────────┐
┌──────────────────┐ │ │ Daily: 250k–1M │
Non-search call ──→ │ Burst: 190/10s │ ──┘ │ (per portal) │
└──────────────────┘ └─────────────────┘
A search call is only metered against the search bucket and the daily bucket. A non-search call (batch read/update, single PATCH, associations, etc.) is only metered against the burst bucket and the daily bucket. The two short-window buckets do not share counters.
What this means practically
What this means practically
Hitting search secondly limit (5/s):
Only further
/search
calls 429. Your batch reads, batch updates, single PATCHes, association calls — all keep working at full burst speed. This is actually useful: if your sync is bottlenecked on Search, you can pivot to using
lastmodifieddate
filters via batch endpoints, or use the IDs you already have to do batch reads, while Search cools off.
Hitting burst limit (190/10s):
Only non-search calls 429. You can still issue Search calls at 5/s. Useful for: continuing to identify deltas for queueing while in-flight updates drain, or refreshing a small set of records via Search even though your write pipeline is paused.
Hitting daily limit:
Everything stops. Search and non-search alike. Doesn't unblock until midnight in your portal's configured timezone — and remember this is shared across every private app in the portal, plus n8n flows, marketing tools, anything else hitting the API with that account.
So can one type of request block the other?
So can one type of request block the other?
Search → Burst:
No.
Saturating Search at 50 calls per 10s leaves your full 190/10s burst untouched.
Burst → Search:
No.
Saturating burst leaves Search's 5/s untouched.
Either → Daily:
Yes
, but only by contributing to the same daily counter. It's volume that kills you here, not the type.
In theory you can sustain ~50 search + ~190 other =
~240 calls per 10 seconds
indefinitely (until daily runs out), because the two buckets bill independently.
Implementation consequence
Implementation consequence
Your rate limiter needs to know which bucket a request hits before it acquires. Cleanest pattern:
Copy to clipboard
php
$limiter
->
acquire
(
'search'
,
$weight
=
1
)
;
// for search endpoints only
$limiter
->
acquire
(
'burst'
,
$weight
=
1
)
;
// for everything else
$limiter
->
acquire
(
'daily'
,
$weight
=
1
)
;
// every call, both kinds
In Redis: three separate keys, three separate Lua acquire scripts (or one script with a bucket parameter). A search call calls
acquire('search')
+
acquire('daily')
. A batch update calls
acquire('burst')
+
acquire('daily')
.
One subtle pitfall: when you backoff on a 429,
inspect
policyName
in the response body
to know which bucket actually fired. If you see
SECONDLY
from a search endpoint, you only need to delay search-bound jobs — burst-bound jobs in the queue can keep running. If you blanket-pause everything on any 429, you waste capacity on the bucket that's still healthy. Two queues (or two throttle keys) make this clean; one global pause is the lazy version.
The other subtlety: the burst response gives you
X-HubSpot-RateLimit-Remaining
to clamp local state against, but search doesn't return those headers. So your search counter is unverified — be slightly more conservative there (e.g., budget 4/s instead of 5) to absorb the unaccounted-for calls.
Copy
Give positive feedback
Give negative feedback
You said: Ok, so what will happen each time I make a request?
You said: Ok, so what will happen each time I make a request?
Ok, so what will happen each time I make a request? Let's say I have 100,000 requests to make that are instant. Do I need to increment each request based on the type? For example, if there is a search request, I need to increment both the search bucket and also the daily bucket. If it's a burst, then I can go to the daily and burst buckets. If I need to check if it can make a call, do I need to see both the daily and search, or burst, respectively? Isn't that too heavy for the infrastructure, adding additional load to each request?
13:44
Edit
Copy
Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.
Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.
Analyzed per-request overhead concerns and quantified Redis performance impact
Analyzed per-request overhead concerns and quantified Redis performance impact
Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out. But the overhead is much smaller than it sounds, and importantly, it's nowhere near your bottleneck.
What "two buckets per request" actually costs
What "two buckets per request" actually costs
Each request makes
one round trip to Redis
— not two — because you fold both bucket checks into a single Lua script that runs atomically server-side:
Copy to clipboard
lua
-- KEYS[1] = burst bucket key (or search)
-- KEYS[2] = daily bucket key
-- ARGV[1] = now (ms) ARGV[2] = window_ms
-- ARGV[3] = bucket_max ARGV[4] = daily_max
-- ARGV[5] = request_id ARGV[6] = daily_ttl
-- Trim sliding window
redis
.
call
(
'ZREMRANGEBYSCORE'
,
KEYS
[
1
]
,
0
,
ARGV
[
1
]
-
ARGV
[
2
]
)
local
burst_used
=
redis
.
call
(
'ZCARD'
,
KEYS
[
1...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"bounds":{"left":0.029587766,"top":0.03830806,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"bounds":{"left":0.10239362,"top":0.06703911,"width":0.030585106,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10239362,"top":0.06703911,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":16,"bounds":{"left":0.10538564,"top":0.06703911,"width":0.027925532,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"bounds":{"left":0.1349734,"top":0.06703911,"width":0.0063164895,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"bounds":{"left":0.10239362,"top":0.079010375,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.10239362,"top":0.079010375,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.10538564,"top":0.079010375,"width":0.022938829,"height":0.011971269}}],"role_description":"text"},{"role":"AXButton","text":"Open sidebar","depth":14,"bounds":{"left":0.029920213,"top":0.02793296,"width":0.00930851,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"bounds":{"left":0.004986702,"top":0.059856344,"width":0.025930852,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"bounds":{"left":0.03158245,"top":0.059856344,"width":0.03125,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"bounds":{"left":0.0631649,"top":0.059856344,"width":0.026928192,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":15,"bounds":{"left":0.0043218085,"top":0.08938547,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":16,"bounds":{"left":0.014295213,"top":0.0933759,"width":0.018949468,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.014295213,"top":0.0933759,"width":0.003656915,"height":0.013567438}},{"char_start":1,"char_count":7,"bounds":{"left":0.01761968,"top":0.0933759,"width":0.015957447,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":17,"bounds":{"left":0.08178192,"top":0.0933759,"width":0.006981383,"height":0.012769354},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Projects","depth":15,"bounds":{"left":0.0043218085,"top":0.110135674,"width":0.08643617,"height":0.019952115},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":15,"bounds":{"left":0.0043218085,"top":0.1300878,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":15,"bounds":{"left":0.0043218085,"top":0.15003991,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"bounds":{"left":0.0063164895,"top":0.18914606,"width":0.08377659,"height":0.013567438},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":18,"bounds":{"left":0.0043218085,"top":0.20590582,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":19,"bounds":{"left":0.08344415,"top":0.20909816,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":18,"bounds":{"left":0.0043218085,"top":0.22745411,"width":0.08643617,"height":0.019952115},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":19,"bounds":{"left":0.08344415,"top":0.22984837,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"bounds":{"left":0.0063164895,"top":0.25698325,"width":0.06349734,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"bounds":{"left":0.07114362,"top":0.25698325,"width":0.018949468,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit implementation strategy","depth":18,"bounds":{"left":0.0043218085,"top":0.27294493,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit implementation strategy","depth":19,"bounds":{"left":0.08344415,"top":0.27613726,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe retention policy code location","depth":18,"bounds":{"left":0.0043218085,"top":0.29449323,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe retention policy code location","depth":19,"bounds":{"left":0.08344415,"top":0.29768556,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Viewing retention policy in screenpipe","depth":18,"bounds":{"left":0.0043218085,"top":0.31524342,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Viewing retention policy in screenpipe","depth":19,"bounds":{"left":0.08344415,"top":0.31843576,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clean shot x video recording termination issue","depth":18,"bounds":{"left":0.0043218085,"top":0.3367917,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Clean shot x video recording termination issue","depth":19,"bounds":{"left":0.08344415,"top":0.33998403,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit handling with executeRequest","depth":18,"bounds":{"left":0.0043218085,"top":0.3575419,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit handling with executeRequest","depth":19,"bounds":{"left":0.08344415,"top":0.36073422,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Untitled","depth":18,"bounds":{"left":0.0043218085,"top":0.3790902,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options","depth":19,"bounds":{"left":0.08344415,"top":0.38228253,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 Screen pipe. Is there ability…","depth":18,"bounds":{"left":0.0043218085,"top":0.39984038,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 Screen pipe. Is there ability…","depth":19,"bounds":{"left":0.08344415,"top":0.40303272,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"SMB mount access inconsistency between Finder and iTerm","depth":18,"bounds":{"left":0.0043218085,"top":0.42138866,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for SMB mount access inconsistency between Finder and iTerm","depth":19,"bounds":{"left":0.08344415,"top":0.4237829,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 What is the best switch I can…","depth":18,"bounds":{"left":0.0043218085,"top":0.44213888,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 What is the best switch I can…","depth":19,"bounds":{"left":0.08344415,"top":0.44533122,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Permission denied on screenpipe volume","depth":18,"bounds":{"left":0.0043218085,"top":0.46288908,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Permission denied on screenpipe volume","depth":19,"bounds":{"left":0.08344415,"top":0.4660814,"width":0.005984043,"height":0.015163607},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync database attachment error","depth":18,"bounds":{"left":0.0043218085,"top":0.48443735,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync database attachment error","depth":19,"bounds":{"left":0.08344415,"top":0.48762968,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Last swimming outing with Dani","depth":18,"bounds":{"left":0.0043218085,"top":0.5051876,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Last swimming outing with Dani","depth":19,"bounds":{"left":0.08344415,"top":0.5083799,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Definition of incarcerated","depth":18,"bounds":{"left":0.0043218085,"top":0.52673584,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Definition of incarcerated","depth":19,"bounds":{"left":0.08344415,"top":0.52992815,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chromecast remote volume buttons not working","depth":18,"bounds":{"left":0.0043218085,"top":0.547486,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Chromecast remote volume buttons not working","depth":19,"bounds":{"left":0.08344415,"top":0.5506784,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Salesforce API errors with Organization and FieldDefinition queries","depth":18,"bounds":{"left":0.0043218085,"top":0.56903434,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Salesforce API errors with Organization and FieldDefinition queries","depth":19,"bounds":{"left":0.08344415,"top":0.57222664,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Daily activity summary from screenpipe data","depth":18,"bounds":{"left":0.0043218085,"top":0.5897845,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Daily activity summary from screenpipe data","depth":19,"bounds":{"left":0.08344415,"top":0.59297687,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"MacBook unexpected restarts and kanji screen","depth":18,"bounds":{"left":0.0043218085,"top":0.6113328,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for MacBook unexpected restarts and kanji screen","depth":19,"bounds":{"left":0.08344415,"top":0.61452514,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Security patch review and testing guidance","depth":18,"bounds":{"left":0.0043218085,"top":0.632083,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Security patch review and testing guidance","depth":19,"bounds":{"left":0.08344415,"top":0.63527536,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Food calorie values reference","depth":18,"bounds":{"left":0.0043218085,"top":0.65363127,"width":0.08643617,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Food calorie values reference","depth":19,"bounds":{"left":0.08344415,"top":0.65682364,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tracking location history from last week","depth":18,"bounds":{"left":0.0043218085,"top":0.6743815,"width":0.08643617,"height":0.011173184},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Tracking location history from last week","depth":19,"bounds":{"left":0.08344415,"top":0.6775738,"width":0.005984043,"height":0.007980846},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Pro","depth":15,"bounds":{"left":0.0043218085,"top":0.6943336,"width":0.037898935,"height":0.01915403},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Get apps and extensions","depth":15,"bounds":{"left":0.08277926,"top":0.6943336,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit implementation strategy, rename chat","depth":19,"bounds":{"left":0.043218084,"top":0.02793296,"width":0.09773936,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"HubSpot rate limit implementation strategy","depth":21,"bounds":{"left":0.04454787,"top":0.031923383,"width":0.09507979,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.04454787,"top":0.031923383,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":41,"bounds":{"left":0.048204787,"top":0.031923383,"width":0.09142287,"height":0.014365523}}],"role_description":"text"},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit implementation strategy","depth":19,"bounds":{"left":0.14128989,"top":0.02793296,"width":0.0066489363,"height":0.022346368},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open sidebar","depth":21,"bounds":{"left":0.48537233,"top":0.026336791,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Claude is responding","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"You said: So just a solution for rate limit implementation.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: So just a solution for rate limit implementation.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"updating activity (engagement, meeting, call, note)","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"searching for a company or contact","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"updating deal","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"patching IDs\nReview what the limit is at the moment in the HubSpot documentation and then think of the solution:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"where to store it","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"how to read it","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"how to work with multiple jobs","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"how to work with paginated requests Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? [API usage guidelines and limits - HubSpot docs](","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines","depth":25,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). Example of such a error Client error:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"POST https://api.hubapi.com/crm/v3/objects/contact/search","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"resulted in a","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"429 Too Many Requests","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT\",\"correlationId\":\"019dffc4-4 (truncated...). Think carefully about the implementation and potential issue and bottlenecks.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10:42","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Current HubSpot limits (Apr 2026)","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Current HubSpot limits (Apr 2026)","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Current HubSpot limits (Apr 2026)","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Current HubSpot limits (Apr 2026)","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"For privately distributed apps (which is what Jiminny presumably uses with HubSpot), the limits are layered: a per-app burst window of 100/10s on Free/Starter, 190/10s on Pro/Enterprise, or 250/10s with the API Limit Increase add-on","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", plus a per-account daily cap of 250k / 625k / 1M depending on tier — shared across all private apps in the account","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". The burst window is per app; the daily quota is per portal.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The Search API is special: five requests per second per account, capped at 200 records per page, with a hard 10,000-result ceiling per query","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". That's the limit the 429 in your example is hitting (","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"secondly limit","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"on","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/crm/v3/objects/contact/search","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). It's a separate bucket from the 190/10s, and critically, search responses do not include the","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"X-HubSpot-RateLimit-*","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"headers","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— you can't read remaining capacity off the response, you have to track it yourself.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Non-search endpoints return","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"X-HubSpot-RateLimit-Remaining","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-Max","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", and","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-Interval-Milliseconds","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". The daily headers are not returned for OAuth-authenticated calls","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", so for those you check the account-info endpoint or maintain your own counter. There's also a soft rule: errors should stay under 5% of total daily requests","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— that's enforced for marketplace listing, but it's also a sane operational target.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXLink","text":"hubspot","depth":26,"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"So you have","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"four buckets","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to track at minimum: app-burst (10s sliding), search-secondly (1s sliding), account-daily (24h fixed, midnight in HubSpot's TZ), and per-app daily share (your own budgeting on top of the account cap).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Where to store it","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Where to store it","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Redis. It's the only realistic option once you have multiple queue workers — DB-backed counters serialize too much, and in-process state can't coordinate across workers. Laravel already speaks Redis natively, and you get atomic","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INCR","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EXPIRE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"plus Lua scripting for true CAS semantics.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Key layout I'd use:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"hubspot:rl:burst:{portalId}:{appId} ZSET (sliding 10s)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot:rl:search:{portalId} ZSET (sliding 1s)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot:rl:daily:{portalId} STRING + TTL to midnight TZ","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot:rl:meta:{portalId} HASH (last seen headers)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The two short-window buckets are sliding-window-log (Redis ZSET, score =","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"microtime","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", member = unique request id). Lua script removes entries older than the window, counts remaining slots, and only adds the new one if there's room — all atomic. Fixed windows are simpler but allow 2× the limit at the boundary, which on a 5/s window is brutal.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The daily bucket is a plain","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INCR","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with TTL set to seconds-until-midnight in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubSpot's account timezone","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", not yours — Sofia is UTC+2/+3 but your portal might be set to US Eastern. Get this once and cache it.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"How to read it","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"How to read it","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Two-phase: optimistic pre-check, authoritative post-update.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Before a call, run the Lua acquire script. If it returns \"denied,\" sleep until a slot frees up (the script can return ms-until-next-slot) or push the job back to the queue with a delay. Don't busy-loop.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"After the call, parse","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"X-HubSpot-RateLimit-Remaining","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-Max","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-Interval-Milliseconds","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and store them in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot:rl:meta:{portalId}","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". This is your reality check — if your local counter says 50 remaining but HubSpot's header says 5, you trust HubSpot and clamp your counter. This handles clock drift, missed accounting (e.g., crashed worker that took a token but never made the request), and other apps in the same account consuming the daily budget invisibly.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"For search specifically, since headers don't come back, the local counter","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"the source of truth — meaning if you ever crash mid-flight, you've under-counted. Always release tokens on connection-level failure but","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"not","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"on 429 (you really did make that request).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"How to work with multiple jobs","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"How to work with multiple jobs","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The queue layer needs to enforce concurrency separately from the rate limiter. Both work together:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Concurrency cap","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"via","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue::throttle()","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"or a Redis semaphore — limits how many sync workers run in parallel against HubSpot. Without this, you can have 50 workers all blocked waiting for tokens, which is wasteful and creates retry storms.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Per-tenant fairness","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— if you sync many Jiminny customers into different HubSpot portals, each portal has its own bucket but you still want one slow portal not to starve others. Either separate queues per portal or a fair-share scheduler.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Priority lanes","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— webhook-driven updates (user-visible latency) should outrank background batch syncs. Two queues:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot-priority","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hubspot-bulk","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", with priority workers taking 70% of the burst budget and bulk taking 30% (enforce via separate sub-buckets if you really want hard separation, otherwise just pull from priority first).","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Backoff on 429","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— respect the","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Retry-After","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"header. If absent, exponential with jitter (e.g.,","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"min(2^attempt * 250ms, 30s) ± 20%","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). The jitter is non-negotiable; without it, simultaneous 429s retry in lockstep and you get thundering-herd 429s on retry.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"How to work with paginated requests","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"How to work with paginated requests","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The trap is treating \"fetch all\" as one logical operation. Each page is its own API call and competes for tokens with everything else.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Two practical rules:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Don't hold the worker idle while paginating.","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"If page N takes 500ms and page N+1 needs to wait 800ms for a token, you've burned 800ms of worker time doing nothing. Instead, fetch page N, dispatch the","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"processing","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"of page N as a separate job, and queue a \"fetch page N+1\" job with a delay equal to the wait time. Each page becomes its own atomic unit.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Always batch where HubSpot offers it.","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"POST /crm/v3/objects/{type}/batch/{read|update|create}","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"accepts up to 100 IDs per call. For your \"patching IDs\" flow this is the difference between 600 calls and 6.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Walkthrough: 600 opportunities","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Walkthrough: 600 opportunities","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Naive flow (one PATCH per deal): 600 calls. At 190/10s that's ~32s of API time, well under daily. You'd hit burst easily without throttling. Plus likely 1-3 search calls upfront and N association calls.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sane flow:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Identify deltas","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— if you can avoid Search at all, do. Use","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"lastmodifieddate","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"filter on a single search if you must, paginate at limit=200. For 600 deals that's 3 search calls @ 5/s = ~600ms naturally throttled.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Batch read","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"current state —","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"POST /crm/v3/objects/deals/batch/read","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with 100 IDs per call → 6 calls. Burst weight 6.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Compute diff","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"in your code (no API).","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Batch update","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"—","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"POST /crm/v3/objects/deals/batch/update","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with 100 per call → 6 calls.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Associations","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"—","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"POST /crm/v4/associations/{from}/{to}/batch/create","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"if needed → another few batch calls.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Total: ~15-20 API calls for 600 deals. Burst budget consumed: 20/190 over ~5s. Daily impact: trivial.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Recognition: pre-check denies if","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"<weight>","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"slots aren't free; on 429 the response surfaces","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"policyName","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(DAILY/SECONDLY) — log it, push the job back with","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Retry-After","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"delay, increment a metric so you can alarm on sustained 429 rate.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Pitfalls & bottlenecks worth pre-empting","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Pitfalls & bottlenecks worth pre-empting","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Daily quota is shared across the whole portal","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", including any n8n flows, other private apps, marketing automation calling APIs, etc. Budget for ~70-80% of the documented limit, not 100%. Add an internal per-app daily cap so a runaway sync can't starve other apps.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Search is uniquely fragile","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": separate 5/s bucket, no headers to read, 10k-result cap per query. If you ever hit 10k+ deals matching a filter, partition by","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"hs_object_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ranges or by","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"lastmodifieddate","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"windows.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Retry storms after HubSpot incidents","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— always jitter, always cap total retries (e.g., 5 attempts, then dead-letter).","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Webhook → sync → webhook loops","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— when your update fires a HubSpot webhook that triggers another sync. Tag your own writes (e.g., a custom property","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"jy_last_sync_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":") and ignore webhooks where it just changed.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Token refresh","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— OAuth refresh itself counts against burst. Cache the access token in Redis with TTL =","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"expires_in - 60s","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", single-flight the refresh (only one worker refreshes; others wait on a lock).","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Search latency for fresh writes","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— newly created/updated objects don't appear in search instantly. Don't write-then-search; use returned IDs directly.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Clock skew","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— Redis is authoritative. Don't use","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"time()","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"from each worker for window scoring; use","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"redis.call('TIME')","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"inside the Lua script.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dead workers with held tokens","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— sliding-window-log handles this naturally because tokens expire by score; token-bucket implementations need explicit release-on-crash logic.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The 5% error rule","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"counts every 429 toward your error budget, even successfully retried ones. If your pre-check is working well, 429s should be near zero — if they're not, your pre-check is wrong, not just slow.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Phantom contention with Laravel","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimited","depth":28,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"middleware","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": it's per-job-class by default, not per-portal. If your","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncDealJob","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateActivityJob","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"are different classes both calling HubSpot, they don't share a Laravel-level limiter — you need a single","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotRateLimiter","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"service that everything routes through.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"If you want, I can sketch the Lua script for the sliding-window acquire and the Laravel service wrapper around it.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: What will happen if there are two different types of rate limits?","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: What will happen if there are two different types of rate limits?","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"What will happen if there are two different types of rate limits?","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Search requests that have 5 requests per second","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The other rate limit that has, let's say, 100 requests per 10 seconds How do these work together? Is there some conflict? Can I reach the limit with one request and block either as well, or are these separated? Can I still continue making search requests once I hit the normal requests limits?","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"13:40","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: They're independent buckets — with one shared bucket on top.","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: They're independent buckets — with one shared bucket on top.","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Analyzed independent rate limit buckets and their practical interactions","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzed independent rate limit buckets and their practical interactions","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"They're independent buckets — with one shared bucket on top.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"The three buckets and what each call hits","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"The three buckets and what each call hits","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"┌──────────────────┐","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Search request ──→ │ Search: 5/sec │ ──┐","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"└──────────────────┘ │","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"├──→ ┌─────────────────┐","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"┌──────────────────┐ │ │ Daily: 250k–1M │","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Non-search call ──→ │ Burst: 190/10s │ ──┘ │ (per portal) │","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"└──────────────────┘ └─────────────────┘","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"A search call is only metered against the search bucket and the daily bucket. A non-search call (batch read/update, single PATCH, associations, etc.) is only metered against the burst bucket and the daily bucket. The two short-window buckets do not share counters.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"What this means practically","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"What this means practically","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hitting search secondly limit (5/s):","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Only further","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"/search","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"calls 429. Your batch reads, batch updates, single PATCHes, association calls — all keep working at full burst speed. This is actually useful: if your sync is bottlenecked on Search, you can pivot to using","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"lastmodifieddate","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"filters via batch endpoints, or use the IDs you already have to do batch reads, while Search cools off.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hitting burst limit (190/10s):","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Only non-search calls 429. You can still issue Search calls at 5/s. Useful for: continuing to identify deltas for queueing while in-flight updates drain, or refreshing a small set of records via Search even though your write pipeline is paused.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hitting daily limit:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Everything stops. Search and non-search alike. Doesn't unblock until midnight in your portal's configured timezone — and remember this is shared across every private app in the portal, plus n8n flows, marketing tools, anything else hitting the API with that account.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"So can one type of request block the other?","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"So can one type of request block the other?","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Search → Burst:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"No.","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saturating Search at 50 calls per 10s leaves your full 190/10s burst untouched.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Burst → Search:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"No.","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saturating burst leaves Search's 5/s untouched.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Either → Daily:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Yes","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", but only by contributing to the same daily counter. It's volume that kills you here, not the type.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"In theory you can sustain ~50 search + ~190 other =","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~240 calls per 10 seconds","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"indefinitely (until daily runs out), because the two buckets bill independently.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"Implementation consequence","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Implementation consequence","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Your rate limiter needs to know which bucket a request hits before it acquires. Cleanest pattern:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"php","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$limiter","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"->","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"'search'","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$weight","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":")","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":";","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"// for search endpoints only","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$limiter","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"->","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"'burst'","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$weight","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":")","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":";","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"// for everything else","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$limiter","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"->","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"'daily'","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$weight","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":")","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":";","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"// every call, both kinds","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"In Redis: three separate keys, three separate Lua acquire scripts (or one script with a bucket parameter). A search call calls","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire('search')","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire('daily')","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". A batch update calls","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire('burst')","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"acquire('daily')","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"One subtle pitfall: when you backoff on a 429,","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"inspect","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"policyName","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"in the response body","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to know which bucket actually fired. If you see","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SECONDLY","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"from a search endpoint, you only need to delay search-bound jobs — burst-bound jobs in the queue can keep running. If you blanket-pause everything on any 429, you waste capacity on the bucket that's still healthy. Two queues (or two throttle keys) make this clean; one global pause is the lazy version.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"The other subtlety: the burst response gives you","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"X-HubSpot-RateLimit-Remaining","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to clamp local state against, but search doesn't return those headers. So your search counter is unverified — be slightly more conservative there (e.g., budget 4/s instead of 5) to absorb the unaccounted-for calls.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: Ok, so what will happen each time I make a request?","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: Ok, so what will happen each time I make a request?","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Ok, so what will happen each time I make a request? Let's say I have 100,000 requests to make that are instant. Do I need to increment each request based on the type? For example, if there is a search request, I need to increment both the search bucket and also the daily bucket. If it's a burst, then I can go to the daily and burst buckets. If I need to check if it can make a call, do I need to see both the daily and search, or burst, respectively? Isn't that too heavy for the infrastructure, adding additional load to each request?","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"13:44","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Analyzed per-request overhead concerns and quantified Redis performance impact","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzed per-request overhead concerns and quantified Redis performance impact","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out. But the overhead is much smaller than it sounds, and importantly, it's nowhere near your bottleneck.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"What \"two buckets per request\" actually costs","depth":24,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"What \"two buckets per request\" actually costs","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Each request makes","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"one round trip to Redis","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— not two — because you fold both bucket checks into a single Lua script that runs atomically server-side:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lua","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- KEYS[1] = burst bucket key (or search)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- KEYS[2] = daily bucket key","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- ARGV[1] = now (ms) ARGV[2] = window_ms","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- ARGV[3] = bucket_max ARGV[4] = daily_max","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- ARGV[5] = request_id ARGV[6] = daily_ttl","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-- Trim sliding window","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"redis","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"call","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"'ZREMRANGEBYSCORE'","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KEYS","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"[","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"]","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ARGV","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"[","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"]","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"-","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ARGV","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"[","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"]","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":")","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"local","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"burst_used","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"redis","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"call","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"'ZCARD'","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KEYS","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"[","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":27,"on_screen":false,"role_description":"text"}]...
|
8939713995209934261
|
-6913360278149309356
|
visual_change
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Open sidebar
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Definition of incarcerated
More options for Definition of incarcerated
Chromecast remote volume buttons not working
More options for Chromecast remote volume buttons not working
Salesforce API errors with Organization and FieldDefinition queries
More options for Salesforce API errors with Organization and FieldDefinition queries
Daily activity summary from screenpipe data
More options for Daily activity summary from screenpipe data
MacBook unexpected restarts and kanji screen
More options for MacBook unexpected restarts and kanji screen
Security patch review and testing guidance
More options for Security patch review and testing guidance
Food calorie values reference
More options for Food calorie values reference
Tracking location history from last week
More options for Tracking location history from last week
Lukas Pro
Get apps and extensions
HubSpot rate limit implementation strategy, rename chat
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Open sidebar
Claude is responding
You said: So just a solution for rate limit implementation.
You said: So just a solution for rate limit implementation.
So just a solution for rate limit implementation. The situation currently is that there are some daily and secondary limits for HubSpot. I would like to make sure I never reach a 429 error rate limit. The requests can come from any flow. It can be:
updating activity (engagement, meeting, call, note)
searching for a company or contact
updating deal
patching IDs
Review what the limit is at the moment in the HubSpot documentation and then think of the solution:
where to store it
how to read it
how to work with multiple jobs
how to work with paginated requests Let's consider a situation when there are 600 opportunities to sync. What would be the flow? How would the rate limit be recognized? How would it be handled? What are the potential issues? [API usage guidelines and limits - HubSpot docs](
https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
). Example of such a error Client error:
POST https://api.hubapi.com/crm/v3/objects/contact/search
resulted in a
429 Too Many Requests
response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019dffc4-4 (truncated...). Think carefully about the implementation and potential issue and bottlenecks.
Show more
10:42
Edit
Copy
Claude responded: Current HubSpot limits (Apr 2026)
Claude responded: Current HubSpot limits (Apr 2026)
Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms
Synthesized HubSpot rate limit architecture with Redis storage and sliding window algorithms
Current HubSpot limits (Apr 2026)
Current HubSpot limits (Apr 2026)
For privately distributed apps (which is what Jiminny presumably uses with HubSpot), the limits are layered: a per-app burst window of 100/10s on Free/Starter, 190/10s on Pro/Enterprise, or 250/10s with the API Limit Increase add-on
, plus a per-account daily cap of 250k / 625k / 1M depending on tier — shared across all private apps in the account
. The burst window is per app; the daily quota is per portal.
hubspot
hubspot
hubspot
hubspot
The Search API is special: five requests per second per account, capped at 200 records per page, with a hard 10,000-result ceiling per query
. That's the limit the 429 in your example is hitting (
secondly limit
on
/crm/v3/objects/contact/search
). It's a separate bucket from the 190/10s, and critically, search responses do not include the
X-HubSpot-RateLimit-*
headers
— you can't read remaining capacity off the response, you have to track it yourself.
hubspot
hubspot
hubspot
hubspot
Non-search endpoints return
X-HubSpot-RateLimit-Remaining
,
-Max
, and
-Interval-Milliseconds
. The daily headers are not returned for OAuth-authenticated calls
, so for those you check the account-info endpoint or maintain your own counter. There's also a soft rule: errors should stay under 5% of total daily requests
— that's enforced for marketplace listing, but it's also a sane operational target.
hubspot
hubspot
hubspot
hubspot
So you have
four buckets
to track at minimum: app-burst (10s sliding), search-secondly (1s sliding), account-daily (24h fixed, midnight in HubSpot's TZ), and per-app daily share (your own budgeting on top of the account cap).
Where to store it
Where to store it
Redis. It's the only realistic option once you have multiple queue workers — DB-backed counters serialize too much, and in-process state can't coordinate across workers. Laravel already speaks Redis natively, and you get atomic
INCR
/
EXPIRE
plus Lua scripting for true CAS semantics.
Key layout I'd use:
Copy to clipboard
hubspot:rl:burst:{portalId}:{appId} ZSET (sliding 10s)
hubspot:rl:search:{portalId} ZSET (sliding 1s)
hubspot:rl:daily:{portalId} STRING + TTL to midnight TZ
hubspot:rl:meta:{portalId} HASH (last seen headers)
The two short-window buckets are sliding-window-log (Redis ZSET, score =
microtime
, member = unique request id). Lua script removes entries older than the window, counts remaining slots, and only adds the new one if there's room — all atomic. Fixed windows are simpler but allow 2× the limit at the boundary, which on a 5/s window is brutal.
The daily bucket is a plain
INCR
with TTL set to seconds-until-midnight in
HubSpot's account timezone
, not yours — Sofia is UTC+2/+3 but your portal might be set to US Eastern. Get this once and cache it.
How to read it
How to read it
Two-phase: optimistic pre-check, authoritative post-update.
Before a call, run the Lua acquire script. If it returns "denied," sleep until a slot frees up (the script can return ms-until-next-slot) or push the job back to the queue with a delay. Don't busy-loop.
After the call, parse
X-HubSpot-RateLimit-Remaining
/
-Max
/
-Interval-Milliseconds
and store them in
hubspot:rl:meta:{portalId}
. This is your reality check — if your local counter says 50 remaining but HubSpot's header says 5, you trust HubSpot and clamp your counter. This handles clock drift, missed accounting (e.g., crashed worker that took a token but never made the request), and other apps in the same account consuming the daily budget invisibly.
For search specifically, since headers don't come back, the local counter
is
the source of truth — meaning if you ever crash mid-flight, you've under-counted. Always release tokens on connection-level failure but
not
on 429 (you really did make that request).
How to work with multiple jobs
How to work with multiple jobs
The queue layer needs to enforce concurrency separately from the rate limiter. Both work together:
Concurrency cap
via
Queue::throttle()
or a Redis semaphore — limits how many sync workers run in parallel against HubSpot. Without this, you can have 50 workers all blocked waiting for tokens, which is wasteful and creates retry storms.
Per-tenant fairness
— if you sync many Jiminny customers into different HubSpot portals, each portal has its own bucket but you still want one slow portal not to starve others. Either separate queues per portal or a fair-share scheduler.
Priority lanes
— webhook-driven updates (user-visible latency) should outrank background batch syncs. Two queues:
hubspot-priority
and
hubspot-bulk
, with priority workers taking 70% of the burst budget and bulk taking 30% (enforce via separate sub-buckets if you really want hard separation, otherwise just pull from priority first).
Backoff on 429
— respect the
Retry-After
header. If absent, exponential with jitter (e.g.,
min(2^attempt * 250ms, 30s) ± 20%
). The jitter is non-negotiable; without it, simultaneous 429s retry in lockstep and you get thundering-herd 429s on retry.
How to work with paginated requests
How to work with paginated requests
The trap is treating "fetch all" as one logical operation. Each page is its own API call and competes for tokens with everything else.
Two practical rules:
Don't hold the worker idle while paginating.
If page N takes 500ms and page N+1 needs to wait 800ms for a token, you've burned 800ms of worker time doing nothing. Instead, fetch page N, dispatch the
processing
of page N as a separate job, and queue a "fetch page N+1" job with a delay equal to the wait time. Each page becomes its own atomic unit.
Always batch where HubSpot offers it.
POST /crm/v3/objects/{type}/batch/{read|update|create}
accepts up to 100 IDs per call. For your "patching IDs" flow this is the difference between 600 calls and 6.
Walkthrough: 600 opportunities
Walkthrough: 600 opportunities
Naive flow (one PATCH per deal): 600 calls. At 190/10s that's ~32s of API time, well under daily. You'd hit burst easily without throttling. Plus likely 1-3 search calls upfront and N association calls.
Sane flow:
Identify deltas
— if you can avoid Search at all, do. Use
lastmodifieddate
filter on a single search if you must, paginate at limit=200. For 600 deals that's 3 search calls @ 5/s = ~600ms naturally throttled.
Batch read
current state —
POST /crm/v3/objects/deals/batch/read
with 100 IDs per call → 6 calls. Burst weight 6.
Compute diff
in your code (no API).
Batch update
—
POST /crm/v3/objects/deals/batch/update
with 100 per call → 6 calls.
Associations
—
POST /crm/v4/associations/{from}/{to}/batch/create
if needed → another few batch calls.
Total: ~15-20 API calls for 600 deals. Burst budget consumed: 20/190 over ~5s. Daily impact: trivial.
Recognition: pre-check denies if
<weight>
slots aren't free; on 429 the response surfaces
policyName
(DAILY/SECONDLY) — log it, push the job back with
Retry-After
delay, increment a metric so you can alarm on sustained 429 rate.
Pitfalls & bottlenecks worth pre-empting
Pitfalls & bottlenecks worth pre-empting
Daily quota is shared across the whole portal
, including any n8n flows, other private apps, marketing automation calling APIs, etc. Budget for ~70-80% of the documented limit, not 100%. Add an internal per-app daily cap so a runaway sync can't starve other apps.
Search is uniquely fragile
: separate 5/s bucket, no headers to read, 10k-result cap per query. If you ever hit 10k+ deals matching a filter, partition by
hs_object_id
ranges or by
lastmodifieddate
windows.
Retry storms after HubSpot incidents
— always jitter, always cap total retries (e.g., 5 attempts, then dead-letter).
Webhook → sync → webhook loops
— when your update fires a HubSpot webhook that triggers another sync. Tag your own writes (e.g., a custom property
jy_last_sync_id
) and ignore webhooks where it just changed.
Token refresh
— OAuth refresh itself counts against burst. Cache the access token in Redis with TTL =
expires_in - 60s
, single-flight the refresh (only one worker refreshes; others wait on a lock).
Search latency for fresh writes
— newly created/updated objects don't appear in search instantly. Don't write-then-search; use returned IDs directly.
Clock skew
— Redis is authoritative. Don't use
time()
from each worker for window scoring; use
redis.call('TIME')
inside the Lua script.
Dead workers with held tokens
— sliding-window-log handles this naturally because tokens expire by score; token-bucket implementations need explicit release-on-crash logic.
The 5% error rule
counts every 429 toward your error budget, even successfully retried ones. If your pre-check is working well, 429s should be near zero — if they're not, your pre-check is wrong, not just slow.
Phantom contention with Laravel
RateLimited
middleware
: it's per-job-class by default, not per-portal. If your
SyncDealJob
and
UpdateActivityJob
are different classes both calling HubSpot, they don't share a Laravel-level limiter — you need a single
HubspotRateLimiter
service that everything routes through.
If you want, I can sketch the Lua script for the sliding-window acquire and the Laravel service wrapper around it.
Copy
Give positive feedback
Give negative feedback
You said: What will happen if there are two different types of rate limits?
You said: What will happen if there are two different types of rate limits?
What will happen if there are two different types of rate limits?
Search requests that have 5 requests per second
The other rate limit that has, let's say, 100 requests per 10 seconds How do these work together? Is there some conflict? Can I reach the limit with one request and block either as well, or are these separated? Can I still continue making search requests once I hit the normal requests limits?
13:40
Edit
Copy
Claude responded: They're independent buckets — with one shared bucket on top.
Claude responded: They're independent buckets — with one shared bucket on top.
Analyzed independent rate limit buckets and their practical interactions
Analyzed independent rate limit buckets and their practical interactions
They're independent buckets — with one shared bucket on top.
The three buckets and what each call hits
The three buckets and what each call hits
Copy to clipboard
┌──────────────────┐
Search request ──→ │ Search: 5/sec │ ──┐
└──────────────────┘ │
├──→ ┌─────────────────┐
┌──────────────────┐ │ │ Daily: 250k–1M │
Non-search call ──→ │ Burst: 190/10s │ ──┘ │ (per portal) │
└──────────────────┘ └─────────────────┘
A search call is only metered against the search bucket and the daily bucket. A non-search call (batch read/update, single PATCH, associations, etc.) is only metered against the burst bucket and the daily bucket. The two short-window buckets do not share counters.
What this means practically
What this means practically
Hitting search secondly limit (5/s):
Only further
/search
calls 429. Your batch reads, batch updates, single PATCHes, association calls — all keep working at full burst speed. This is actually useful: if your sync is bottlenecked on Search, you can pivot to using
lastmodifieddate
filters via batch endpoints, or use the IDs you already have to do batch reads, while Search cools off.
Hitting burst limit (190/10s):
Only non-search calls 429. You can still issue Search calls at 5/s. Useful for: continuing to identify deltas for queueing while in-flight updates drain, or refreshing a small set of records via Search even though your write pipeline is paused.
Hitting daily limit:
Everything stops. Search and non-search alike. Doesn't unblock until midnight in your portal's configured timezone — and remember this is shared across every private app in the portal, plus n8n flows, marketing tools, anything else hitting the API with that account.
So can one type of request block the other?
So can one type of request block the other?
Search → Burst:
No.
Saturating Search at 50 calls per 10s leaves your full 190/10s burst untouched.
Burst → Search:
No.
Saturating burst leaves Search's 5/s untouched.
Either → Daily:
Yes
, but only by contributing to the same daily counter. It's volume that kills you here, not the type.
In theory you can sustain ~50 search + ~190 other =
~240 calls per 10 seconds
indefinitely (until daily runs out), because the two buckets bill independently.
Implementation consequence
Implementation consequence
Your rate limiter needs to know which bucket a request hits before it acquires. Cleanest pattern:
Copy to clipboard
php
$limiter
->
acquire
(
'search'
,
$weight
=
1
)
;
// for search endpoints only
$limiter
->
acquire
(
'burst'
,
$weight
=
1
)
;
// for everything else
$limiter
->
acquire
(
'daily'
,
$weight
=
1
)
;
// every call, both kinds
In Redis: three separate keys, three separate Lua acquire scripts (or one script with a bucket parameter). A search call calls
acquire('search')
+
acquire('daily')
. A batch update calls
acquire('burst')
+
acquire('daily')
.
One subtle pitfall: when you backoff on a 429,
inspect
policyName
in the response body
to know which bucket actually fired. If you see
SECONDLY
from a search endpoint, you only need to delay search-bound jobs — burst-bound jobs in the queue can keep running. If you blanket-pause everything on any 429, you waste capacity on the bucket that's still healthy. Two queues (or two throttle keys) make this clean; one global pause is the lazy version.
The other subtlety: the burst response gives you
X-HubSpot-RateLimit-Remaining
to clamp local state against, but search doesn't return those headers. So your search counter is unverified — be slightly more conservative there (e.g., budget 4/s instead of 5) to absorb the unaccounted-for calls.
Copy
Give positive feedback
Give negative feedback
You said: Ok, so what will happen each time I make a request?
You said: Ok, so what will happen each time I make a request?
Ok, so what will happen each time I make a request? Let's say I have 100,000 requests to make that are instant. Do I need to increment each request based on the type? For example, if there is a search request, I need to increment both the search bucket and also the daily bucket. If it's a burst, then I can go to the daily and burst buckets. If I need to check if it can make a call, do I need to see both the daily and search, or burst, respectively? Isn't that too heavy for the infrastructure, adding additional load to each request?
13:44
Edit
Copy
Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.
Claude responded: Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out.
Analyzed per-request overhead concerns and quantified Redis performance impact
Analyzed per-request overhead concerns and quantified Redis performance impact
Yes, your understanding is correct — every request must check and reserve from each bucket it applies to, atomically, before the call goes out. But the overhead is much smaller than it sounds, and importantly, it's nowhere near your bottleneck.
What "two buckets per request" actually costs
What "two buckets per request" actually costs
Each request makes
one round trip to Redis
— not two — because you fold both bucket checks into a single Lua script that runs atomically server-side:
Copy to clipboard
lua
-- KEYS[1] = burst bucket key (or search)
-- KEYS[2] = daily bucket key
-- ARGV[1] = now (ms) ARGV[2] = window_ms
-- ARGV[3] = bucket_max ARGV[4] = daily_max
-- ARGV[5] = request_id ARGV[6] = daily_ttl
-- Trim sliding window
redis
.
call
(
'ZREMRANGEBYSCORE'
,
KEYS
[
1
]
,
0
,
ARGV
[
1
]
-
ARGV
[
2
]
)
local
burst_used
=
redis
.
call
(
'ZCARD'
,
KEYS
[
1...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
27034
|
1124
|
12
|
2026-05-12T14:02:06.723482+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778594526723_m1.jpg...
|
Firefox
|
TypeError: League\Flysystem\Filesystem::has(): Arg TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app — Work...
|
True
|
jiminny.sentry.io/issues/6873095751/events/?projec jiminny.sentry.io/issues/6873095751/events/?project=82419&query=&statsPeriod=24h...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfiles→ToolsW FirefoxFileEditViewHistoryBookmarksProfiles→ToolsWindowHelp(ad]meet.google.com/bdj-nvho-bms?authuser=lukas.kovalik%40jiminny.comRetro - Platform - now100% C8 • Tue 12 May 17:02:06÷Pop out this videoSteliyan GeorgievNikolay YankovStefka Stoyanova*Lukas Kovalik5:02 PM | Retro - Platform• 1:38Lộ3...
|
NULL
|
8939542447103600246
|
NULL
|
visual_change
|
ocr
|
NULL
|
FirefoxFileEditViewHistoryBookmarksProfiles→ToolsW FirefoxFileEditViewHistoryBookmarksProfiles→ToolsWindowHelp(ad]meet.google.com/bdj-nvho-bms?authuser=lukas.kovalik%40jiminny.comRetro - Platform - now100% C8 • Tue 12 May 17:02:06÷Pop out this videoSteliyan GeorgievNikolay YankovStefka Stoyanova*Lukas Kovalik5:02 PM | Retro - Platform• 1:38Lộ3...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
25642
|
1077
|
6
|
2026-05-12T11:41:32.255495+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778586092255_m2.jpg...
|
Claude
|
Claude
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Screenpipe sync script failing after recent migrations
More options for Screenpipe sync script failing after recent migrations
Hubspot BadRequest headers debugging
More options for Hubspot BadRequest headers debugging
Monthly expense tracking
More options for Monthly expense tracking
Exporting transaction data from Notion to finance hub
More options for Exporting transaction data from Notion to finance hub
💬 How much have I spent for groc…
More options for 💬 How much have I spent for groc…
April 2026 spending by category
More options for April 2026 spending by category
Code diff review
More options for Code diff review
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Lukas Pro
Get apps and extensions
Screenpipe data sync and retention management, rename chat
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Open sidebar
Share chat
Claude finished the response
You said: Lets start from the beginning with the fresh mind.
You said: Lets start from the beginning with the fresh mind.
Pasted Text, pasted, 495 lines
#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'
PASTED
Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need
20:19
Retry
Edit
Copy
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Analyzed three technical challenges and identified clarifying questions needed
Analyzed three technical challenges and identified clarifying questions needed
Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:
Issue 1 — ID collisions / archive rotation
Has a reinstall already happened, or is the current
archive.db
still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.
Quarterly rotation works, but it doesn't actually solve reinstalls
within
a quarter. A cleaner option that I'd lean toward: add an
install_id
column (UUID generated once and stored in a tiny
_meta
table in the source DB) to every synced table, and make
(install_id, id)
the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.
Do you ever run cross-period queries (e.g. "every frame mentioning JY-20458 across all time")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.
Issue 2 — Mac-side retention
Your launch command already has
--retention-days 7
. Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?
Should retention also wipe the matching
~/.screenpipe/data/data/YYYY-MM-DD/
folders, or only DB rows? (CLI flag does both I believe; a script should match.)
Run order matters: sync writes
yesterday
, retention deletes
>7 days old
— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?
Issue 3 — Audio
Your current launch flag is
--disable-audio
, so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?
I need the audio schema. Could you run this and paste the output:
Copy to clipboard
sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription"
And:
ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
so I can see what audio files look like on disk alongside the mp4 frames.
Once I have those, I'll restructure the script in one go rather than piecemeal.
Copy
Give positive feedback
Give negative feedback
Retry
You said: ok lets add install_id.
You said: ok lets add install_id.
Pasted Text, pasted, 208 lines
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev
PASTED
ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
compact_monitor_1_1778411210025.mp4
compact_monitor_1_1778413422547.mp4
compact_monitor_1_1778436664801.mp4
compact_monitor_1_1778437267451.mp4
compact_monitor_1_1778437569681.mp4
compact_monitor_1_1778437873507.mp4
compact_monitor_1_1778438175846.mp4
compact_monitor_1_1778438477299.mp4
compact_monitor_1_1778438780686.mp4
compact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4
drwxr-xr-x 9 lukas staff 288 11 May 07:54 data
drwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions
-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4
-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4
-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4
-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4
-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4
-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4
-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4
-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...
Show more
20:34
Retry
Edit
Copy
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Architected multi-install sync with audio support and retention
Architected multi-install sync with audio support and retention
Good, that clarifies the audio layout — audio files live flat in
~/.screenpipe/data/
with the date in the filename (not in date subdirs like frames), and the schema gives us
audio_chunks
+
audio_transcriptions
+
speakers
+
speaker_embeddings
+
audio_tags
plus the FTS table on transcriptions.
Plan I'll implement:
install_id
: stored as a UUID in
~/.screenpipe/.sync_install_id
— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an
install_id TEXT
column and a
UNIQUE(install_id, id)
index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with
legacy-YYYYMMDD
so historical data is still queryable but doesn't conflict with future installs.
Audio sync
: adds
audio_chunks
,
audio_transcriptions
,
speakers
,
speaker_embeddings
,
audio_tags
to the sync pipeline, plus the
audio_transcriptions_fts
index. Audio files go to
$NAS_DATA/<date>/audio/
(sibling to the frames in the same date dir, keeps daily archives self-contained).
Retention
is a separate manual script — dry-run by default,
--apply
to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional
--vacuum
with a warning (screenpipe must be stopped to avoid blocking writes for minutes).
One caveat I'm punting on: the FTS tables use source
id
as
rowid
, so after a reinstall they'd get id-collisions on insert and
INSERT OR IGNORE
would silently skip. Within a single install this is fine; after reinstall you'd fall back to
LIKE
queries on the base tables (still filterable by
install_id
). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.
Let me write both:
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
screenpipe_sync.sh
Done
Copy
Give positive feedback
Give negative feedback
Retry
You said: I don't like that.
You said: I don't like that.
I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?
14:26
Retry
Edit
Copy
Claude responded: You're right — the FTS tables are the gap I papered over.
Claude responded: You're right — the FTS tables are the gap I papered over.
Architected collision-free FTS schema with contentless tables and install tracking
Architected collision-free FTS schema with contentless tables and install tracking
You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.
What I missed:
SQLite FTS tables use an
INTEGER rowid
as their primary key. The previous design used the source
id
as that rowid (e.g.
INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)
). After a reinstall, source IDs reset to 1, and
INSERT OR IGNORE
silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.
Fix:
switch all four FTS tables to fully contentless mode with
install_id
and
source_id
as
UNINDEXED
columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:
Copy to clipboard
sql
SELECT
f
.
*
FROM
nas
.
frames f
JOIN
nas
.
frames_fts fts
ON
fts
.
install_id
=
f
.
install_id
AND
fts
.
source_id
=...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"bounds":{"left":0.50232714,"top":0.025538707,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"bounds":{"left":0.50232714,"top":0.026336791,"width":0.030917553,"height":0.013567438},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.50232714,"top":0.027134877,"width":0.0026595744,"height":0.012769354}},{"char_start":1,"char_count":14,"bounds":{"left":0.5049867,"top":0.027134877,"width":0.02825798,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"bounds":{"left":0.6023936,"top":0.3216281,"width":0.030917553,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6023936,"top":0.3216281,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":16,"bounds":{"left":0.60538566,"top":0.3216281,"width":0.027925532,"height":0.011971269}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"bounds":{"left":0.6349734,"top":0.3216281,"width":0.0066489363,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"bounds":{"left":0.6023936,"top":0.33359936,"width":0.025930852,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6023936,"top":0.33359936,"width":0.0029920214,"height":0.011971269}},{"char_start":1,"char_count":13,"bounds":{"left":0.60538566,"top":0.33359936,"width":0.022938829,"height":0.011971269}}],"role_description":"text"},{"role":"AXButton","text":"Collapse sidebar","depth":15,"bounds":{"left":0.5305851,"top":0.02952913,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search","depth":15,"bounds":{"left":0.53856385,"top":0.02952913,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"bounds":{"left":0.50598407,"top":0.06304868,"width":0.026263298,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"bounds":{"left":0.53291225,"top":0.06304868,"width":0.031914894,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"bounds":{"left":0.56515956,"top":0.06304868,"width":0.027260639,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":15,"bounds":{"left":0.5053192,"top":0.096568234,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":16,"bounds":{"left":0.5152925,"top":0.09976058,"width":0.019614361,"height":0.013567438},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.5152925,"top":0.10055866,"width":0.0033244682,"height":0.013567438}},{"char_start":1,"char_count":7,"bounds":{"left":0.51861703,"top":0.10055866,"width":0.015957447,"height":0.013567438}}],"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":17,"bounds":{"left":0.5844415,"top":0.10055866,"width":0.0066489363,"height":0.011971269},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Projects","depth":15,"bounds":{"left":0.5053192,"top":0.11731844,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":15,"bounds":{"left":0.5053192,"top":0.13806863,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":15,"bounds":{"left":0.5053192,"top":0.15881884,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"bounds":{"left":0.50731385,"top":0.19872306,"width":0.08510638,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":18,"bounds":{"left":0.5053192,"top":0.21548285,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":19,"bounds":{"left":0.58577126,"top":0.21867518,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":18,"bounds":{"left":0.5053192,"top":0.23703113,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":19,"bounds":{"left":0.58577126,"top":0.24022347,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"bounds":{"left":0.50731385,"top":0.26735833,"width":0.06482713,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"bounds":{"left":0.5731383,"top":0.26735833,"width":0.019281914,"height":0.012769354},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe data sync and retention management","depth":18,"bounds":{"left":0.5053192,"top":0.28411812,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe data sync and retention management","depth":19,"bounds":{"left":0.58577126,"top":0.28731045,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync script failing after recent migrations","depth":18,"bounds":{"left":0.5053192,"top":0.3056664,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync script failing after recent migrations","depth":19,"bounds":{"left":0.58577126,"top":0.30885875,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hubspot BadRequest headers debugging","depth":18,"bounds":{"left":0.5053192,"top":0.3272147,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Hubspot BadRequest headers debugging","depth":19,"bounds":{"left":0.58577126,"top":0.33040702,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Monthly expense tracking","depth":18,"bounds":{"left":0.5053192,"top":0.34876296,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Monthly expense tracking","depth":19,"bounds":{"left":0.58577126,"top":0.3519553,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Exporting transaction data from Notion to finance hub","depth":18,"bounds":{"left":0.5053192,"top":0.37031126,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Exporting transaction data from Notion to finance hub","depth":19,"bounds":{"left":0.58577126,"top":0.3735036,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 How much have I spent for groc…","depth":18,"bounds":{"left":0.5053192,"top":0.39185953,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 How much have I spent for groc…","depth":19,"bounds":{"left":0.58577126,"top":0.39505187,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"April 2026 spending by category","depth":18,"bounds":{"left":0.5053192,"top":0.41340783,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for April 2026 spending by category","depth":19,"bounds":{"left":0.58577126,"top":0.41660017,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code diff review","depth":18,"bounds":{"left":0.5053192,"top":0.4349561,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Code diff review","depth":19,"bounds":{"left":0.58577126,"top":0.43814844,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit implementation strategy","depth":18,"bounds":{"left":0.5053192,"top":0.45650437,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit implementation strategy","depth":19,"bounds":{"left":0.58577126,"top":0.45969674,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe retention policy code location","depth":18,"bounds":{"left":0.5053192,"top":0.47805268,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe retention policy code location","depth":19,"bounds":{"left":0.58577126,"top":0.481245,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Viewing retention policy in screenpipe","depth":18,"bounds":{"left":0.5053192,"top":0.49960095,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Viewing retention policy in screenpipe","depth":19,"bounds":{"left":0.58577126,"top":0.5027933,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clean shot x video recording termination issue","depth":18,"bounds":{"left":0.5053192,"top":0.5211492,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Clean shot x video recording termination issue","depth":19,"bounds":{"left":0.58577126,"top":0.5243416,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit handling with executeRequest","depth":18,"bounds":{"left":0.5053192,"top":0.54269755,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit handling with executeRequest","depth":19,"bounds":{"left":0.58577126,"top":0.54588985,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Untitled","depth":18,"bounds":{"left":0.5053192,"top":0.5642458,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options","depth":19,"bounds":{"left":0.58577126,"top":0.5674381,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 Screen pipe. Is there ability…","depth":18,"bounds":{"left":0.5053192,"top":0.5857941,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 Screen pipe. Is there ability…","depth":19,"bounds":{"left":0.58577126,"top":0.58898646,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"SMB mount access inconsistency between Finder and iTerm","depth":18,"bounds":{"left":0.5053192,"top":0.60734236,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for SMB mount access inconsistency between Finder and iTerm","depth":19,"bounds":{"left":0.58577126,"top":0.6105347,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 What is the best switch I can…","depth":18,"bounds":{"left":0.5053192,"top":0.62889063,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 What is the best switch I can…","depth":19,"bounds":{"left":0.58577126,"top":0.632083,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Permission denied on screenpipe volume","depth":18,"bounds":{"left":0.5053192,"top":0.65043896,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Permission denied on screenpipe volume","depth":19,"bounds":{"left":0.58577126,"top":0.65363127,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync database attachment error","depth":18,"bounds":{"left":0.5053192,"top":0.67198724,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync database attachment error","depth":19,"bounds":{"left":0.58577126,"top":0.67517954,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Last swimming outing with Dani","depth":18,"bounds":{"left":0.5053192,"top":0.6935355,"width":0.087765954,"height":0.0207502},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Last swimming outing with Dani","depth":19,"bounds":{"left":0.58577126,"top":0.6967279,"width":0.005984043,"height":0.014365523},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Pro","depth":15,"bounds":{"left":0.5053192,"top":0.9696728,"width":0.03856383,"height":0.01915403},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Get apps and extensions","depth":15,"bounds":{"left":0.5851064,"top":0.9696728,"width":0.007978723,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe data sync and retention management, rename chat","depth":19,"bounds":{"left":0.6023936,"top":0.02793296,"width":0.11070479,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Screenpipe data sync and retention management","depth":21,"bounds":{"left":0.6037234,"top":0.031923383,"width":0.10804521,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6037234,"top":0.031923383,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":44,"bounds":{"left":0.60671544,"top":0.031923383,"width":0.105053194,"height":0.014365523}}],"role_description":"text"},{"role":"AXPopUpButton","text":"More options for Screenpipe data sync and retention management","depth":19,"bounds":{"left":0.7134308,"top":0.02793296,"width":0.006981383,"height":0.022346368},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open sidebar","depth":21,"bounds":{"left":0.9734042,"top":0.026336791,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share chat","depth":21,"bounds":{"left":0.98537236,"top":0.026336791,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Claude finished the response","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"You said: Lets start from the beginning with the fresh mind.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: Lets start from the beginning with the fresh mind.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Pasted Text, pasted, 495 lines","depth":21,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PASTED","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"20:19","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Analyzed three technical challenges and identified clarifying questions needed","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzed three technical challenges and identified clarifying questions needed","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 1 — ID collisions / archive rotation","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Has a reinstall already happened, or is the current","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"archive.db","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Quarterly rotation works, but it doesn't actually solve reinstalls","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"within","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"a quarter. A cleaner option that I'd lean toward: add an","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"column (UUID generated once and stored in a tiny","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"_meta","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"table in the source DB) to every synced table, and make","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(install_id, id)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Do you ever run cross-period queries (e.g. \"every frame mentioning JY-20458 across all time\")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 2 — Mac-side retention","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Your launch command already has","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--retention-days 7","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Should retention also wipe the matching","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/data/data/YYYY-MM-DD/","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"folders, or only DB rows? (CLI flag does both I believe; a script should match.)","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Run order matters: sync writes","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"yesterday","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", retention deletes","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":">7 days old","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 3 — Audio","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Your current launch flag is","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--disable-audio","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"I need the audio schema. Could you run this and paste the output:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite \".schema\" | grep -iE -A 20 \"audio|transcription\"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"And:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"so I can see what audio files look like on disk alongside the mp4 frames.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Once I have those, I'll restructure the script in one go rather than piecemeal.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: ok lets add install_id.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: ok lets add install_id.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Pasted Text, pasted, 208 lines","depth":21,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema\" | grep -iE -A 20 \"audio|transcription\" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PASTED","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head\ncompact_monitor_1_1778411210025.mp4\ncompact_monitor_1_1778413422547.mp4\ncompact_monitor_1_1778436664801.mp4\ncompact_monitor_1_1778437267451.mp4\ncompact_monitor_1_1778437569681.mp4\ncompact_monitor_1_1778437873507.mp4\ncompact_monitor_1_1778438175846.mp4\ncompact_monitor_1_1778438477299.mp4\ncompact_monitor_1_1778438780686.mp4\ncompact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4\ndrwxr-xr-x 9 lukas staff 288 11 May 07:54 data\ndrwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions\n-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4\n-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4\n-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4\n-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4\n-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4\n-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4\n-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4\n-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4\n-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"20:34","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Architected multi-install sync with audio support and retention","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Architected multi-install sync with audio support and retention","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Good, that clarifies the audio layout — audio files live flat in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/data/","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with the date in the filename (not in date subdirs like frames), and the schema gives us","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_chunks","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speakers","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speaker_embeddings","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_tags","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"plus the FTS table on transcriptions.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Plan I'll implement:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": stored as a UUID in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/.sync_install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id TEXT","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"column and a","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UNIQUE(install_id, id)","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"legacy-YYYYMMDD","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"so historical data is still queryable but doesn't conflict with future installs.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Audio sync","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": adds","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_chunks","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speakers","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speaker_embeddings","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_tags","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to the sync pipeline, plus the","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions_fts","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"index. Audio files go to","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$NAS_DATA/<date>/audio/","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(sibling to the frames in the same date dir, keeps daily archives self-contained).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Retention","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is a separate manual script — dry-run by default,","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--apply","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--vacuum","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with a warning (screenpipe must be stopped to avoid blocking writes for minutes).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"One caveat I'm punting on: the FTS tables use source","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"as","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"rowid","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", so after a reinstall they'd get id-collisions on insert and","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INSERT OR IGNORE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"would silently skip. Within a single install this is fine; after reinstall you'd fall back to","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LIKE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"queries on the base tables (still filterable by","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Let me write both:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":23,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"screenpipe_sync.sh","depth":24,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Done","depth":24,"bounds":{"left":0.68949467,"top":0.0311253,"width":0.011968086,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.68949467,"top":0.031923383,"width":0.003656915,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.6931516,"top":0.031923383,"width":0.007978723,"height":0.014365523}}],"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.6768617,"top":0.06863528,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"bounds":{"left":0.6875,"top":0.06863528,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"bounds":{"left":0.6981383,"top":0.06863528,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":22,"bounds":{"left":0.7087766,"top":0.06863528,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: I don't like that.","depth":20,"bounds":{"left":0.6765292,"top":0.112529926,"width":0.0003324468,"height":0.0015961692},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"You said: I don't like that.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?","depth":24,"bounds":{"left":0.71875,"top":0.123703115,"width":0.19448139,"height":0.03431764},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.71875,"top":0.1245012,"width":0.0016622341,"height":0.015961692}},{"char_start":1,"char_count":108,"bounds":{"left":0.71875,"top":0.1245012,"width":0.19481383,"height":0.03431764}}],"role_description":"text"},{"role":"AXStaticText","text":"14:26","depth":22,"bounds":{"left":0.87765956,"top":0.17797287,"width":0.00930851,"height":0.012769354},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.87765956,"top":0.17877094,"width":0.0016622341,"height":0.011971269}},{"char_start":1,"char_count":4,"bounds":{"left":0.87898934,"top":0.17877094,"width":0.00831117,"height":0.011971269}}],"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"bounds":{"left":0.88962764,"top":0.17158818,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"bounds":{"left":0.90026593,"top":0.17158818,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"bounds":{"left":0.9109042,"top":0.17158818,"width":0.010638298,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: You're right — the FTS tables are the gap I papered over.","depth":21,"bounds":{"left":0.6765292,"top":0.19952115,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: You're right — the FTS tables are the gap I papered over.","depth":22,"bounds":{"left":0.6765292,"top":0.19952115,"width":0.18716756,"height":0.0007980846},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Architected collision-free FTS schema with contentless tables and install tracking","depth":22,"bounds":{"left":0.67952126,"top":0.20510775,"width":0.24202128,"height":0.022346368},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Architected collision-free FTS schema with contentless tables and install tracking","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.","depth":25,"bounds":{"left":0.67952126,"top":0.23383878,"width":0.23138298,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.67952126,"top":0.23383878,"width":0.0033244682,"height":0.016759777}},{"char_start":1,"char_count":96,"bounds":{"left":0.6828458,"top":0.23383878,"width":0.22805852,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"What I missed:","depth":26,"bounds":{"left":0.67952126,"top":0.26256984,"width":0.037898935,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.67952126,"top":0.26256984,"width":0.005984043,"height":0.016759777}},{"char_start":1,"char_count":13,"bounds":{"left":0.68550533,"top":0.26256984,"width":0.031914894,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"SQLite FTS tables use an","depth":25,"bounds":{"left":0.7174202,"top":0.26256984,"width":0.0631649,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7174202,"top":0.26256984,"width":0.0009973404,"height":0.016759777}},{"char_start":1,"char_count":23,"bounds":{"left":0.7184175,"top":0.26256984,"width":0.057513297,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"INTEGER rowid","depth":26,"bounds":{"left":0.7819149,"top":0.26336792,"width":0.03723404,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7819149,"top":0.264166,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":12,"bounds":{"left":0.78457445,"top":0.264166,"width":0.034906916,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"as their primary key. The previous design used the source","depth":25,"bounds":{"left":0.67952126,"top":0.26256984,"width":0.22573139,"height":0.035115723},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.82047874,"top":0.26256984,"width":0.0013297872,"height":0.016759777}},{"char_start":1,"char_count":56,"bounds":{"left":0.67952126,"top":0.26256984,"width":0.22573139,"height":0.035913806}}],"role_description":"text"},{"role":"AXStaticText","text":"id","depth":26,"bounds":{"left":0.7380319,"top":0.28252193,"width":0.005984043,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7380319,"top":0.28332004,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":1,"bounds":{"left":0.74102396,"top":0.28332004,"width":0.0029920214,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"as that rowid (e.g.","depth":25,"bounds":{"left":0.7453458,"top":0.28172386,"width":0.045212764,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7453458,"top":0.28172386,"width":0.0009973404,"height":0.016759777}},{"char_start":1,"char_count":18,"bounds":{"left":0.7463431,"top":0.28172386,"width":0.041888297,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)","depth":26,"bounds":{"left":0.67952126,"top":0.28252193,"width":0.23337767,"height":0.03431764},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7918883,"top":0.28332004,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":54,"bounds":{"left":0.67952126,"top":0.28332004,"width":0.2330452,"height":0.033519555}}],"role_description":"text"},{"role":"AXStaticText","text":"). After a reinstall, source IDs reset to 1, and","depth":25,"bounds":{"left":0.7184175,"top":0.3008779,"width":0.10638298,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7184175,"top":0.3008779,"width":0.0023271276,"height":0.016759777}},{"char_start":1,"char_count":47,"bounds":{"left":0.72041225,"top":0.3008779,"width":0.10305851,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"INSERT OR IGNORE","depth":26,"bounds":{"left":0.82613033,"top":0.30167598,"width":0.045877658,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.82613033,"top":0.30247405,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":15,"bounds":{"left":0.8287899,"top":0.30247405,"width":0.043218084,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.","depth":25,"bounds":{"left":0.67952126,"top":0.3008779,"width":0.22839096,"height":0.07342378},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.87333775,"top":0.3008779,"width":0.0013297872,"height":0.016759777}},{"char_start":1,"char_count":260,"bounds":{"left":0.67952126,"top":0.3008779,"width":0.22839096,"height":0.074221864}}],"role_description":"text"},{"role":"AXStaticText","text":"Fix:","depth":26,"bounds":{"left":0.67952126,"top":0.38707104,"width":0.009973404,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.67952126,"top":0.38707104,"width":0.003656915,"height":0.016759777}},{"char_start":1,"char_count":3,"bounds":{"left":0.6831782,"top":0.38707104,"width":0.0063164895,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"switch all four FTS tables to fully contentless mode with","depth":25,"bounds":{"left":0.68916225,"top":0.38707104,"width":0.13896276,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.68916225,"top":0.38707104,"width":0.0013297872,"height":0.016759777}},{"char_start":1,"char_count":56,"bounds":{"left":0.69015956,"top":0.38707104,"width":0.13364361,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"bounds":{"left":0.8294548,"top":0.38786912,"width":0.028922873,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.8294548,"top":0.3886672,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.8324468,"top":0.3886672,"width":0.025930852,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"and","depth":25,"bounds":{"left":0.8597075,"top":0.38707104,"width":0.011635638,"height":0.015961692},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"source_id","depth":26,"bounds":{"left":0.87267286,"top":0.38786912,"width":0.025930852,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.87267286,"top":0.3886672,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":8,"bounds":{"left":0.8756649,"top":0.3886672,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"as","depth":25,"bounds":{"left":0.8999335,"top":0.38707104,"width":0.0066489363,"height":0.015961692},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.8999335,"top":0.38707104,"width":0.0013297872,"height":0.016759777}},{"char_start":1,"char_count":1,"bounds":{"left":0.9009308,"top":0.38707104,"width":0.0029920214,"height":0.016759777}}],"role_description":"text"},{"role":"AXStaticText","text":"UNINDEXED","depth":26,"bounds":{"left":0.6811835,"top":0.40702313,"width":0.025930852,"height":0.015163607},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6811835,"top":0.40782124,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":8,"bounds":{"left":0.6838431,"top":0.40782124,"width":0.023271276,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:","depth":25,"bounds":{"left":0.67952126,"top":0.40622506,"width":0.22739361,"height":0.035115723},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7084442,"top":0.40622506,"width":0.0009973404,"height":0.016759777}},{"char_start":1,"char_count":123,"bounds":{"left":0.67952126,"top":0.40622506,"width":0.22739361,"height":0.035913806}}],"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"bounds":{"left":0.90824467,"top":0.45889863,"width":0.010638298,"height":0.026336791},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"sql","depth":26,"bounds":{"left":0.6818484,"top":0.46448523,"width":0.0056515955,"height":0.011971269},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6818484,"top":0.46448523,"width":0.0019946808,"height":0.012769354}},{"char_start":1,"char_count":2,"bounds":{"left":0.6838431,"top":0.46448523,"width":0.0033244682,"height":0.012769354}}],"role_description":"text"},{"role":"AXStaticText","text":"SELECT","depth":27,"bounds":{"left":0.6818484,"top":0.49002394,"width":0.01662234,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6818484,"top":0.49082202,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":5,"bounds":{"left":0.68450797,"top":0.49082202,"width":0.013962766,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"f","depth":27,"bounds":{"left":0.6984708,"top":0.49002394,"width":0.0056515955,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.70412236,"top":0.49002394,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"*","depth":27,"bounds":{"left":0.7067819,"top":0.49002394,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"bounds":{"left":0.70977396,"top":0.49002394,"width":0.0026595744,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"FROM","depth":27,"bounds":{"left":0.7124335,"top":0.49002394,"width":0.011303191,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7124335,"top":0.49082202,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.7150931,"top":0.49082202,"width":0.008643617,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"nas","depth":27,"bounds":{"left":0.7237367,"top":0.49002394,"width":0.011303191,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.7347075,"top":0.49002394,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"frames f","depth":27,"bounds":{"left":0.73769945,"top":0.49002394,"width":0.022273935,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.73769945,"top":0.49082202,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":7,"bounds":{"left":0.74035907,"top":0.49082202,"width":0.019614361,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"JOIN","depth":27,"bounds":{"left":0.6818484,"top":0.5083799,"width":0.011303191,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.6818484,"top":0.509178,"width":0.0026595744,"height":0.014365523}},{"char_start":1,"char_count":3,"bounds":{"left":0.68450797,"top":0.509178,"width":0.00831117,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"nas","depth":27,"bounds":{"left":0.6928192,"top":0.5083799,"width":0.011303191,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.70412236,"top":0.5083799,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"frames_fts fts","depth":27,"bounds":{"left":0.7067819,"top":0.5083799,"width":0.042220745,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7067819,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":13,"bounds":{"left":0.70977396,"top":0.509178,"width":0.036236703,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"ON","depth":27,"bounds":{"left":0.7486702,"top":0.5083799,"width":0.0056515955,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.7486702,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":1,"bounds":{"left":0.75166225,"top":0.509178,"width":0.0029920214,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"fts","depth":27,"bounds":{"left":0.7543218,"top":0.5083799,"width":0.011303191,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.765625,"top":0.5083799,"width":0.0026595744,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"bounds":{"left":0.76828456,"top":0.5083799,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.76828456,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.7709442,"top":0.509178,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"bounds":{"left":0.79886967,"top":0.5083799,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"f","depth":27,"bounds":{"left":0.8018617,"top":0.5083799,"width":0.0056515955,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.8075133,"top":0.5083799,"width":0.0026595744,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"bounds":{"left":0.81017286,"top":0.5083799,"width":0.030917553,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.81017286,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":9,"bounds":{"left":0.8128325,"top":0.509178,"width":0.025265958,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"AND","depth":27,"bounds":{"left":0.84075797,"top":0.5083799,"width":0.008643617,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.84075797,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":2,"bounds":{"left":0.84375,"top":0.509178,"width":0.0056515955,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"fts","depth":27,"bounds":{"left":0.8494016,"top":0.5083799,"width":0.011303191,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"bounds":{"left":0.86037236,"top":0.5083799,"width":0.0029920214,"height":0.014365523},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"source_id","depth":27,"bounds":{"left":0.86336434,"top":0.5083799,"width":0.027925532,"height":0.014365523},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.86336434,"top":0.509178,"width":0.0029920214,"height":0.014365523}},{"char_start":1,"char_count":8,"bounds":{"left":0.86602396,"top":0.509178,"width":0.022606382,"height":0.014365523}}],"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"bounds":{"left":0.8912899,"top":0.5083799,"width":0.0026595744,"height":0.014365523},"on_screen":true,"role_description":"text"}]...
|
8938966522759781413
|
-8210416633940836649
|
click
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Screenpipe sync script failing after recent migrations
More options for Screenpipe sync script failing after recent migrations
Hubspot BadRequest headers debugging
More options for Hubspot BadRequest headers debugging
Monthly expense tracking
More options for Monthly expense tracking
Exporting transaction data from Notion to finance hub
More options for Exporting transaction data from Notion to finance hub
💬 How much have I spent for groc…
More options for 💬 How much have I spent for groc…
April 2026 spending by category
More options for April 2026 spending by category
Code diff review
More options for Code diff review
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Lukas Pro
Get apps and extensions
Screenpipe data sync and retention management, rename chat
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Open sidebar
Share chat
Claude finished the response
You said: Lets start from the beginning with the fresh mind.
You said: Lets start from the beginning with the fresh mind.
Pasted Text, pasted, 495 lines
#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'
PASTED
Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need
20:19
Retry
Edit
Copy
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Analyzed three technical challenges and identified clarifying questions needed
Analyzed three technical challenges and identified clarifying questions needed
Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:
Issue 1 — ID collisions / archive rotation
Has a reinstall already happened, or is the current
archive.db
still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.
Quarterly rotation works, but it doesn't actually solve reinstalls
within
a quarter. A cleaner option that I'd lean toward: add an
install_id
column (UUID generated once and stored in a tiny
_meta
table in the source DB) to every synced table, and make
(install_id, id)
the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.
Do you ever run cross-period queries (e.g. "every frame mentioning JY-20458 across all time")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.
Issue 2 — Mac-side retention
Your launch command already has
--retention-days 7
. Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?
Should retention also wipe the matching
~/.screenpipe/data/data/YYYY-MM-DD/
folders, or only DB rows? (CLI flag does both I believe; a script should match.)
Run order matters: sync writes
yesterday
, retention deletes
>7 days old
— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?
Issue 3 — Audio
Your current launch flag is
--disable-audio
, so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?
I need the audio schema. Could you run this and paste the output:
Copy to clipboard
sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription"
And:
ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
so I can see what audio files look like on disk alongside the mp4 frames.
Once I have those, I'll restructure the script in one go rather than piecemeal.
Copy
Give positive feedback
Give negative feedback
Retry
You said: ok lets add install_id.
You said: ok lets add install_id.
Pasted Text, pasted, 208 lines
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev
PASTED
ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
compact_monitor_1_1778411210025.mp4
compact_monitor_1_1778413422547.mp4
compact_monitor_1_1778436664801.mp4
compact_monitor_1_1778437267451.mp4
compact_monitor_1_1778437569681.mp4
compact_monitor_1_1778437873507.mp4
compact_monitor_1_1778438175846.mp4
compact_monitor_1_1778438477299.mp4
compact_monitor_1_1778438780686.mp4
compact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4
drwxr-xr-x 9 lukas staff 288 11 May 07:54 data
drwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions
-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4
-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4
-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4
-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4
-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4
-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4
-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4
-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...
Show more
20:34
Retry
Edit
Copy
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Architected multi-install sync with audio support and retention
Architected multi-install sync with audio support and retention
Good, that clarifies the audio layout — audio files live flat in
~/.screenpipe/data/
with the date in the filename (not in date subdirs like frames), and the schema gives us
audio_chunks
+
audio_transcriptions
+
speakers
+
speaker_embeddings
+
audio_tags
plus the FTS table on transcriptions.
Plan I'll implement:
install_id
: stored as a UUID in
~/.screenpipe/.sync_install_id
— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an
install_id TEXT
column and a
UNIQUE(install_id, id)
index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with
legacy-YYYYMMDD
so historical data is still queryable but doesn't conflict with future installs.
Audio sync
: adds
audio_chunks
,
audio_transcriptions
,
speakers
,
speaker_embeddings
,
audio_tags
to the sync pipeline, plus the
audio_transcriptions_fts
index. Audio files go to
$NAS_DATA/<date>/audio/
(sibling to the frames in the same date dir, keeps daily archives self-contained).
Retention
is a separate manual script — dry-run by default,
--apply
to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional
--vacuum
with a warning (screenpipe must be stopped to avoid blocking writes for minutes).
One caveat I'm punting on: the FTS tables use source
id
as
rowid
, so after a reinstall they'd get id-collisions on insert and
INSERT OR IGNORE
would silently skip. Within a single install this is fine; after reinstall you'd fall back to
LIKE
queries on the base tables (still filterable by
install_id
). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.
Let me write both:
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
screenpipe_sync.sh
Done
Copy
Give positive feedback
Give negative feedback
Retry
You said: I don't like that.
You said: I don't like that.
I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?
14:26
Retry
Edit
Copy
Claude responded: You're right — the FTS tables are the gap I papered over.
Claude responded: You're right — the FTS tables are the gap I papered over.
Architected collision-free FTS schema with contentless tables and install tracking
Architected collision-free FTS schema with contentless tables and install tracking
You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.
What I missed:
SQLite FTS tables use an
INTEGER rowid
as their primary key. The previous design used the source
id
as that rowid (e.g.
INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)
). After a reinstall, source IDs reset to 1, and
INSERT OR IGNORE
silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.
Fix:
switch all four FTS tables to fully contentless mode with
install_id
and
source_id
as
UNINDEXED
columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:
Copy to clipboard
sql
SELECT
f
.
*
FROM
nas
.
frames f
JOIN
nas
.
frames_fts fts
ON
fts
.
install_id
=
f
.
install_id
AND
fts
.
source_id
=...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
26117
|
1084
|
34
|
2026-05-12T12:04:16.542173+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778587456542_m1.jpg...
|
Claude
|
Claude
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Screenpipe sync script failing after recent migrations
More options for Screenpipe sync script failing after recent migrations
Hubspot BadRequest headers debugging
More options for Hubspot BadRequest headers debugging
Monthly expense tracking
More options for Monthly expense tracking
Exporting transaction data from Notion to finance hub
More options for Exporting transaction data from Notion to finance hub
💬 How much have I spent for groc…
More options for 💬 How much have I spent for groc…
April 2026 spending by category
More options for April 2026 spending by category
Code diff review
More options for Code diff review
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Lukas Pro
Get apps and extensions
Screenpipe data sync and retention management, rename chat
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Open sidebar
Share chat
Claude finished the response
You said: Lets start from the beginning with the fresh mind.
You said: Lets start from the beginning with the fresh mind.
Pasted Text, pasted, 495 lines
#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'
PASTED
Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need
20:19
Retry
Edit
Copy
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Analyzed three technical challenges and identified clarifying questions needed
Analyzed three technical challenges and identified clarifying questions needed
Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:
Issue 1 — ID collisions / archive rotation
Has a reinstall already happened, or is the current
archive.db
still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.
Quarterly rotation works, but it doesn't actually solve reinstalls
within
a quarter. A cleaner option that I'd lean toward: add an
install_id
column (UUID generated once and stored in a tiny
_meta
table in the source DB) to every synced table, and make
(install_id, id)
the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.
Do you ever run cross-period queries (e.g. "every frame mentioning JY-20458 across all time")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.
Issue 2 — Mac-side retention
Your launch command already has
--retention-days 7
. Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?
Should retention also wipe the matching
~/.screenpipe/data/data/YYYY-MM-DD/
folders, or only DB rows? (CLI flag does both I believe; a script should match.)
Run order matters: sync writes
yesterday
, retention deletes
>7 days old
— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?
Issue 3 — Audio
Your current launch flag is
--disable-audio
, so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?
I need the audio schema. Could you run this and paste the output:
Copy to clipboard
sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription"
And:
ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
so I can see what audio files look like on disk alongside the mp4 frames.
Once I have those, I'll restructure the script in one go rather than piecemeal.
Copy
Give positive feedback
Give negative feedback
Retry
You said: ok lets add install_id.
You said: ok lets add install_id.
Pasted Text, pasted, 208 lines
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev
PASTED
ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
compact_monitor_1_1778411210025.mp4
compact_monitor_1_1778413422547.mp4
compact_monitor_1_1778436664801.mp4
compact_monitor_1_1778437267451.mp4
compact_monitor_1_1778437569681.mp4
compact_monitor_1_1778437873507.mp4
compact_monitor_1_1778438175846.mp4
compact_monitor_1_1778438477299.mp4
compact_monitor_1_1778438780686.mp4
compact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4
drwxr-xr-x 9 lukas staff 288 11 May 07:54 data
drwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions
-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4
-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4
-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4
-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4
-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4
-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4
-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4
-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...
Show more
20:34
Retry
Edit
Copy
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Architected multi-install sync with audio support and retention
Architected multi-install sync with audio support and retention
Good, that clarifies the audio layout — audio files live flat in
~/.screenpipe/data/
with the date in the filename (not in date subdirs like frames), and the schema gives us
audio_chunks
+
audio_transcriptions
+
speakers
+
speaker_embeddings
+
audio_tags
plus the FTS table on transcriptions.
Plan I'll implement:
install_id
: stored as a UUID in
~/.screenpipe/.sync_install_id
— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an
install_id TEXT
column and a
UNIQUE(install_id, id)
index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with
legacy-YYYYMMDD
so historical data is still queryable but doesn't conflict with future installs.
Audio sync
: adds
audio_chunks
,
audio_transcriptions
,
speakers
,
speaker_embeddings
,
audio_tags
to the sync pipeline, plus the
audio_transcriptions_fts
index. Audio files go to
$NAS_DATA/<date>/audio/
(sibling to the frames in the same date dir, keeps daily archives self-contained).
Retention
is a separate manual script — dry-run by default,
--apply
to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional
--vacuum
with a warning (screenpipe must be stopped to avoid blocking writes for minutes).
One caveat I'm punting on: the FTS tables use source
id
as
rowid
, so after a reinstall they'd get id-collisions on insert and
INSERT OR IGNORE
would silently skip. Within a single install this is fine; after reinstall you'd fall back to
LIKE
queries on the base tables (still filterable by
install_id
). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.
Let me write both:
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
screenpipe_sync.sh
Done
Copy
Give positive feedback
Give negative feedback
Retry
You said: I don't like that.
You said: I don't like that.
I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?
14:26
Retry
Edit
Copy
Claude responded: You're right — the FTS tables are the gap I papered over.
Claude responded: You're right — the FTS tables are the gap I papered over.
Architected collision-free FTS schema with contentless tables and install tracking
Architected collision-free FTS schema with contentless tables and install tracking
You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.
What I missed:
SQLite FTS tables use an
INTEGER rowid
as their primary key. The previous design used the source
id
as that rowid (e.g.
INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)
). After a reinstall, source IDs reset to 1, and
INSERT OR IGNORE
silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.
Fix:
switch all four FTS tables to fully contentless mode with
install_id
and
source_id
as
UNINDEXED
columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:
Copy to clipboard
sql
SELECT
f
.
*
FROM
nas
.
frames f
JOIN
nas
.
frames_fts fts
ON
fts
.
install_id
=
f
.
install_id
AND
fts
.
source_id
=...
|
[{"role":"AXLink","text":& [{"role":"AXLink","text":"Skip to content","depth":14,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Skip to content","depth":15,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Click to collapse","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"⌘B","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drag to resize","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Collapse sidebar","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Chat","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cowork","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New chat ⌘N","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"New chat","depth":16,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"⌘N","depth":17,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Projects","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Artifacts","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Customize","depth":15,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pinned","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Bulgarian citizenship application process for EU residents","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Bulgarian citizenship application process for EU residents","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Dawarich location tracking project","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Dawarich location tracking project","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Recents","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"View all","depth":16,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe data sync and retention management","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe data sync and retention management","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync script failing after recent migrations","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync script failing after recent migrations","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hubspot BadRequest headers debugging","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Hubspot BadRequest headers debugging","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Monthly expense tracking","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Monthly expense tracking","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Exporting transaction data from Notion to finance hub","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Exporting transaction data from Notion to finance hub","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 How much have I spent for groc…","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 How much have I spent for groc…","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"April 2026 spending by category","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for April 2026 spending by category","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code diff review","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Code diff review","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit implementation strategy","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit implementation strategy","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe retention policy code location","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe retention policy code location","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Viewing retention policy in screenpipe","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Viewing retention policy in screenpipe","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Clean shot x video recording termination issue","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Clean shot x video recording termination issue","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"HubSpot rate limit handling with executeRequest","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for HubSpot rate limit handling with executeRequest","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Untitled","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 Screen pipe. Is there ability…","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 Screen pipe. Is there ability…","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"SMB mount access inconsistency between Finder and iTerm","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for SMB mount access inconsistency between Finder and iTerm","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"💬 What is the best switch I can…","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for 💬 What is the best switch I can…","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Permission denied on screenpipe volume","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Permission denied on screenpipe volume","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe sync database attachment error","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Screenpipe sync database attachment error","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Last swimming outing with Dani","depth":18,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"More options for Last swimming outing with Dani","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXPopUpButton","text":"Lukas Pro","depth":15,"bounds":{"left":0.49097222,"top":0.0,"width":0.08055556,"height":0.026666667},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Get apps and extensions","depth":15,"bounds":{"left":0.6576389,"top":0.0,"width":0.016666668,"height":0.026666667},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Screenpipe data sync and retention management, rename chat","depth":19,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Screenpipe data sync and retention management","depth":21,"on_screen":true,"role_description":"text"},{"role":"AXPopUpButton","text":"More options for Screenpipe data sync and retention management","depth":19,"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open sidebar","depth":21,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Share chat","depth":21,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Claude finished the response","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXHeading","text":"You said: Lets start from the beginning with the fresh mind.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: Lets start from the beginning with the fresh mind.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Pasted Text, pasted, 495 lines","depth":21,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PASTED","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"20:19","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Analyzed three technical challenges and identified clarifying questions needed","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzed three technical challenges and identified clarifying questions needed","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 1 — ID collisions / archive rotation","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Has a reinstall already happened, or is the current","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"archive.db","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Quarterly rotation works, but it doesn't actually solve reinstalls","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"within","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"a quarter. A cleaner option that I'd lean toward: add an","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"column (UUID generated once and stored in a tiny","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"_meta","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"table in the source DB) to every synced table, and make","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(install_id, id)","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Do you ever run cross-period queries (e.g. \"every frame mentioning JY-20458 across all time\")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 2 — Mac-side retention","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Your launch command already has","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--retention-days 7","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":". Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Should retention also wipe the matching","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/data/data/YYYY-MM-DD/","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"folders, or only DB rows? (CLI flag does both I believe; a script should match.)","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Run order matters: sync writes","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"yesterday","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", retention deletes","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":">7 days old","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Issue 3 — Audio","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Your current launch flag is","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--disable-audio","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"I need the audio schema. Could you run this and paste the output:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"sqlite3 ~/.screenpipe/db.sqlite \".schema\" | grep -iE -A 20 \"audio|transcription\"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"And:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"so I can see what audio files look like on disk alongside the mp4 frames.","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Once I have those, I'll restructure the script in one go rather than piecemeal.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: ok lets add install_id.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: ok lets add install_id.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Pasted Text, pasted, 208 lines","depth":21,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite \".schema\" | grep -iE -A 20 \"audio|transcription\" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PASTED","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head\ncompact_monitor_1_1778411210025.mp4\ncompact_monitor_1_1778413422547.mp4\ncompact_monitor_1_1778436664801.mp4\ncompact_monitor_1_1778437267451.mp4\ncompact_monitor_1_1778437569681.mp4\ncompact_monitor_1_1778437873507.mp4\ncompact_monitor_1_1778438175846.mp4\ncompact_monitor_1_1778438477299.mp4\ncompact_monitor_1_1778438780686.mp4\ncompact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4\n-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4\n-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4\ndrwxr-xr-x 9 lukas staff 288 11 May 07:54 data\ndrwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions\n-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4\n-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4\n-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4\n-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4\n-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4\n-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4\n-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4\n-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4\n-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Show more","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"20:34","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Architected multi-install sync with audio support and retention","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Architected multi-install sync with audio support and retention","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Good, that clarifies the audio layout — audio files live flat in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/data/","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with the date in the filename (not in date subdirs like frames), and the schema gives us","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_chunks","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speakers","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speaker_embeddings","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"+","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_tags","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"plus the FTS table on transcriptions.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Plan I'll implement:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": stored as a UUID in","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"~/.screenpipe/.sync_install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id TEXT","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"column and a","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UNIQUE(install_id, id)","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"legacy-YYYYMMDD","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"so historical data is still queryable but doesn't conflict with future installs.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Audio sync","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":": adds","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_chunks","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speakers","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"speaker_embeddings","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":",","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_tags","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to the sync pipeline, plus the","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"audio_transcriptions_fts","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"index. Audio files go to","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"$NAS_DATA/<date>/audio/","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"(sibling to the frames in the same date dir, keeps daily archives self-contained).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Retention","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"is a separate manual script — dry-run by default,","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--apply","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"--vacuum","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"with a warning (screenpipe must be stopped to avoid blocking writes for minutes).","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"One caveat I'm punting on: the FTS tables use source","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"as","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"rowid","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":", so after a reinstall they'd get id-collisions on insert and","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INSERT OR IGNORE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"would silently skip. Within a single install this is fine; after reinstall you'd fall back to","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LIKE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"queries on the base tables (still filterable by","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Let me write both:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Updated screenpipe_sync.sh with install_id support and audio sync","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"screenpipe_sync.sh","depth":23,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Done","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give positive feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Give negative feedback","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"You said: I don't like that.","depth":20,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"You said: I don't like that.","depth":21,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?","depth":24,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"14:26","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Retry","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Edit","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Copy","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Claude responded: You're right — the FTS tables are the gap I papered over.","depth":21,"on_screen":false,"role_description":"heading"},{"role":"AXStaticText","text":"Claude responded: You're right — the FTS tables are the gap I papered over.","depth":22,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Architected collision-free FTS schema with contentless tables and install tracking","depth":22,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Architected collision-free FTS schema with contentless tables and install tracking","depth":23,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"What I missed:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SQLite FTS tables use an","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INTEGER rowid","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"as their primary key. The previous design used the source","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"as that rowid (e.g.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"). After a reinstall, source IDs reset to 1, and","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"INSERT OR IGNORE","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Fix:","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"switch all four FTS tables to fully contentless mode with","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"and","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"source_id","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"as","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UNINDEXED","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:","depth":25,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Copy to clipboard","depth":27,"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"sql","depth":26,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SELECT","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"f","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"*","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FROM","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"nas","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"frames f","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JOIN","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"nas","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"frames_fts fts","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ON","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"fts","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"f","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"install_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AND","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"fts","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"source_id","depth":27,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"=","depth":27,"on_screen":false,"role_description":"text"}]...
|
8938966522759781413
|
-8210416633940836649
|
idle
|
accessibility
|
NULL
|
Skip to content
Skip to content
Click to collapse
Skip to content
Skip to content
Click to collapse
⌘B
Drag to resize
Collapse sidebar
Search
Chat
Cowork
Code
New chat ⌘N
New chat
⌘N
Projects
Artifacts
Customize
Pinned
Bulgarian citizenship application process for EU residents
More options for Bulgarian citizenship application process for EU residents
Dawarich location tracking project
More options for Dawarich location tracking project
Recents
View all
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Screenpipe sync script failing after recent migrations
More options for Screenpipe sync script failing after recent migrations
Hubspot BadRequest headers debugging
More options for Hubspot BadRequest headers debugging
Monthly expense tracking
More options for Monthly expense tracking
Exporting transaction data from Notion to finance hub
More options for Exporting transaction data from Notion to finance hub
💬 How much have I spent for groc…
More options for 💬 How much have I spent for groc…
April 2026 spending by category
More options for April 2026 spending by category
Code diff review
More options for Code diff review
HubSpot rate limit implementation strategy
More options for HubSpot rate limit implementation strategy
Screenpipe retention policy code location
More options for Screenpipe retention policy code location
Viewing retention policy in screenpipe
More options for Viewing retention policy in screenpipe
Clean shot x video recording termination issue
More options for Clean shot x video recording termination issue
HubSpot rate limit handling with executeRequest
More options for HubSpot rate limit handling with executeRequest
Untitled
More options
💬 Screen pipe. Is there ability…
More options for 💬 Screen pipe. Is there ability…
SMB mount access inconsistency between Finder and iTerm
More options for SMB mount access inconsistency between Finder and iTerm
💬 What is the best switch I can…
More options for 💬 What is the best switch I can…
Permission denied on screenpipe volume
More options for Permission denied on screenpipe volume
Screenpipe sync database attachment error
More options for Screenpipe sync database attachment error
Last swimming outing with Dani
More options for Last swimming outing with Dani
Lukas Pro
Get apps and extensions
Screenpipe data sync and retention management, rename chat
Screenpipe data sync and retention management
More options for Screenpipe data sync and retention management
Open sidebar
Share chat
Claude finished the response
You said: Lets start from the beginning with the fresh mind.
You said: Lets start from the beginning with the fresh mind.
Pasted Text, pasted, 495 lines
#!/bin/bash # screenpipe_sync.sh # Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions). # Also copies the day's video/frame data folder to the NAS. # # Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB, # the NAS archive gets ALTER TABLE'
PASTED
Lets start from the beginning with the fresh mind. There is a script for screenpipe to copy data for day from mac to nas. There are two issues I want to work with. Firsts if I reinstall the mac version it starts from fresh id 1. That causes some issues. How to make sure the data are valid. I don't want some overcomplicated flow. I was thinking about making new db every 3 months. Secodn issue is that I wan to implement cleanup (retention policy) on mac original screenpipe db. I want script that will remove data that are 7 days old or older for example. ANd lastly I want to extedn the script to work with audio as well. What od you need
20:19
Retry
Edit
Copy
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Claude responded: Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether…
Analyzed three technical challenges and identified clarifying questions needed
Analyzed three technical challenges and identified clarifying questions needed
Good plan to tackle these together — they interact (retention timing depends on sync timing, audio sync depends on schema shape, ID strategy depends on whether reinstalls have already happened). Before I touch the script, a few things:
Issue 1 — ID collisions / archive rotation
Has a reinstall already happened, or is the current
archive.db
still from one continuous install? If collisions already exist I need to handle that first; if not, we have a clean slate.
Quarterly rotation works, but it doesn't actually solve reinstalls
within
a quarter. A cleaner option that I'd lean toward: add an
install_id
column (UUID generated once and stored in a tiny
_meta
table in the source DB) to every synced table, and make
(install_id, id)
the logical key. FK joins just gain one extra predicate. Reinstall → new install_id → no collisions, ever. You can still rotate the archive DB quarterly for file-size reasons, but it's no longer load-bearing.
Do you ever run cross-period queries (e.g. "every frame mentioning JY-20458 across all time")? That answers whether quarterly sharding is acceptable or whether one growing DB is better.
Issue 2 — Mac-side retention
Your launch command already has
--retention-days 7
. Is that not actually deleting anything, or do you want a separate scripted retention you can trust/observe independently?
Should retention also wipe the matching
~/.screenpipe/data/data/YYYY-MM-DD/
folders, or only DB rows? (CLI flag does both I believe; a script should match.)
Run order matters: sync writes
yesterday
, retention deletes
>7 days old
— no overlap, but I'd still chain them (sync → retention → log) in one cron entry so a failed sync blocks the delete. OK with that?
Issue 3 — Audio
Your current launch flag is
--disable-audio
, so right now there's no audio data to sync. Are you planning to enable it, or is this just to future-proof the script?
I need the audio schema. Could you run this and paste the output:
Copy to clipboard
sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription"
And:
ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
so I can see what audio files look like on disk alongside the mp4 frames.
Once I have those, I'll restructure the script in one go rather than piecemeal.
Copy
Give positive feedback
Give negative feedback
Retry
You said: ok lets add install_id.
You said: ok lets add install_id.
Pasted Text, pasted, 208 lines
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ sqlite3 ~/.screenpipe/db.sqlite ".schema" | grep -iE -A 20 "audio|transcription" CREATE TABLE audio_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL , timestamp TIMESTAMP, sync_id TEXT, machine_id TEXT, synced_at DATETIME, ev
PASTED
ok lets add install_id. Lets add separate script deleting data again from data and it should also remove logs and data. Make it manual script for now. I will remove --disable-audio from alias and run it without. I wan to have audio as well. lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ ls ~/.screenpipe/data/data/$(date -v-1d +%Y-%m-%d)/ | head
compact_monitor_1_1778411210025.mp4
compact_monitor_1_1778413422547.mp4
compact_monitor_1_1778436664801.mp4
compact_monitor_1_1778437267451.mp4
compact_monitor_1_1778437569681.mp4
compact_monitor_1_1778437873507.mp4
compact_monitor_1_1778438175846.mp4
compact_monitor_1_1778438477299.mp4
compact_monitor_1_1778438780686.mp4
compact_monitor_1_1778439082442.mp4 Inside ~/,screenpipe/data there are data like this ... -rw-r--r-- 1 lukas staff 4628 11 May 16:48 System Audio (output)_2026-05-11_13-48-12.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:48 System Audio (output)_2026-05-11_13-48-34.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:49 System Audio (output)_2026-05-11_13-48-56.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:49 System Audio (output)_2026-05-11_13-49-19.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-49-41.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:50 System Audio (output)_2026-05-11_13-50-03.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:50 System Audio (output)_2026-05-11_13-50-26.mp4
-rw-r--r-- 1 lukas staff 4628 11 May 16:51 System Audio (output)_2026-05-11_13-50-48.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-10.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:51 System Audio (output)_2026-05-11_13-51-33.mp4
-rw-r--r-- 1 lukas staff 4620 11 May 16:52 System Audio (output)_2026-05-11_13-51-55.mp4
drwxr-xr-x 9 lukas staff 288 11 May 07:54 data
drwxr-xr-x 2 lukas staff 64 11 May 15:48 pending-transcriptions
-rw-r--r-- 1 lukas staff 29419 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
-rw-r--r-- 1 lukas staff 56479 10 May 14:11 soundcore AeroClip (input)_2026-05-10_11-11-05.mp4
-rw-r--r--@ 1 lukas staff 181831 10 May 14:12 soundcore AeroClip (input)_2026-05-10_11-11-35.mp4
-rw-r--r-- 1 lukas staff 149782 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-42-53.mp4
-rw-r--r-- 1 lukas staff 91059 10 May 14:43 soundcore AeroClip (input)_2026-05-10_11-43-25.mp4
-rw-r--r-- 1 lukas staff 30604 10 May 14:44 soundcore AeroClip (input)_2026-05-10_11-44-25.mp4
-rw-r--r-- 1 lukas staff 93813 10 May 14:45 soundcore AeroClip (input)_2026-05-10_11-44-55.mp4
-rw-r--r-- 1 lukas staff 40444 10 May 21:11 soundcore AeroClip (input)_2026-05-10_18-11-18.mp4
-rw-r--r-- 1 lukas staff 193020 10 May 21:12 soundcore AeroClip (input)_2026-05-10_18-11-48.mp4...
Show more
20:34
Retry
Edit
Copy
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Claude responded: Good, that clarifies the audio layout — audio files live flat in ~/.
Architected multi-install sync with audio support and retention
Architected multi-install sync with audio support and retention
Good, that clarifies the audio layout — audio files live flat in
~/.screenpipe/data/
with the date in the filename (not in date subdirs like frames), and the schema gives us
audio_chunks
+
audio_transcriptions
+
speakers
+
speaker_embeddings
+
audio_tags
plus the FTS table on transcriptions.
Plan I'll implement:
install_id
: stored as a UUID in
~/.screenpipe/.sync_install_id
— disappears when the directory is wiped (which is what you want on reinstall). Every synced NAS table gets an
install_id TEXT
column and a
UNIQUE(install_id, id)
index. The script auto-migrates the existing NAS archive by adding the column and tagging legacy rows with
legacy-YYYYMMDD
so historical data is still queryable but doesn't conflict with future installs.
Audio sync
: adds
audio_chunks
,
audio_transcriptions
,
speakers
,
speaker_embeddings
,
audio_tags
to the sync pipeline, plus the
audio_transcriptions_fts
index. Audio files go to
$NAS_DATA/<date>/audio/
(sibling to the frames in the same date dir, keeps daily archives self-contained).
Retention
is a separate manual script — dry-run by default,
--apply
to delete. Hits DB rows, frame directories, audio mp4s, and screenpipe logs. Optional
--vacuum
with a warning (screenpipe must be stopped to avoid blocking writes for minutes).
One caveat I'm punting on: the FTS tables use source
id
as
rowid
, so after a reinstall they'd get id-collisions on insert and
INSERT OR IGNORE
would silently skip. Within a single install this is fine; after reinstall you'd fall back to
LIKE
queries on the base tables (still filterable by
install_id
). I noted this in the script. Fixing it properly needs a synthetic rowid scheme which we can do later if you actually hit it.
Let me write both:
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
Updated screenpipe_sync.sh with install_id support and audio sync
screenpipe_sync.sh
Done
Copy
Give positive feedback
Give negative feedback
Retry
You said: I don't like that.
You said: I don't like that.
I don't like that. It should be solid without id collisions. Wouldn't it be to late if I should fix it later?
14:26
Retry
Edit
Copy
Claude responded: You're right — the FTS tables are the gap I papered over.
Claude responded: You're right — the FTS tables are the gap I papered over.
Architected collision-free FTS schema with contentless tables and install tracking
Architected collision-free FTS schema with contentless tables and install tracking
You're right — the FTS tables are the gap I papered over. Let me explain why and fix it properly.
What I missed:
SQLite FTS tables use an
INTEGER rowid
as their primary key. The previous design used the source
id
as that rowid (e.g.
INSERT INTO frames_fts(rowid, ...) VALUES (NEW.id, ...)
). After a reinstall, source IDs reset to 1, and
INSERT OR IGNORE
silently drops them because rowid=1 is already taken. So FTS would only index your most recent install — searching for old transcriptions or OCR text would miss anything from before the reinstall, and there's no way to detect the loss because it fails silently.
Fix:
switch all four FTS tables to fully contentless mode with
install_id
and
source_id
as
UNINDEXED
columns, and let SQLite auto-assign rowids. No collision is possible because each insert gets a fresh rowid. Queries become:
Copy to clipboard
sql
SELECT
f
.
*
FROM
nas
.
frames f
JOIN
nas
.
frames_fts fts
ON
fts
.
install_id
=
f
.
install_id
AND
fts
.
source_id
=...
|
26115
|
NULL
|
NULL
|
NULL
|
|
612
|
24
|
44
|
2026-05-07T07:19:55.172562+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778138395172_m2.jpg...
|
Firefox
|
Moje predplatné | Denník N — Personal
|
True
|
predplatne.dennikn.sk/subscriptions/subscriptions/ predplatne.dennikn.sk/subscriptions/subscriptions/my...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
Home | Hostinger
Home | Hostinger
Login – Nginx Proxy Manager
Login – Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Moje predplatné | Denník N
Moje predplatné | Denník N
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Preskočiť hlavnú navigáciu
Preskočiť hlavnú navigáciu
Denník N
Denník N
Denník N
[EMAIL]
[EMAIL]
Predplatné
Predplatné
Platby
Platby
Newslettre
Newslettre
Témy a autori
Témy a autori
Adresa, faktúry
Adresa, faktúry
Nastavenia
Nastavenia
Odhlásiť
Odhlásiť
Predĺžiť predplatné
Predĺžiť predplatné
Zmeniť predplatné / Upgrade
Zmeniť predplatné / Upgrade
História predplatných
História predplatných
Predplatné
Od
Do
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
7. mája 2026, 17:59:50
4. júna 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
9. apríla 2026, 17:59:50
Aktuálne
7. mája 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
12. marca 2026, 17:59:50
9. apríla 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
12. februára 2026, 17:59:50
12. marca 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
15. januára 2026, 17:59:50
12. februára 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
18. decembra 2025, 17:59:50
15. januára 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR
25. septembra 2025, 17:59:50
18. decembra 2025, 17:59:50
Web
Štandard
Mobilné aplikácie
Týždeň
Štandard Denník N a .týždeň 1 mesiac (v3)
15. mája 2025, 19:52:30
15. júna 2025, 19:52:30
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac za 7,99 (inapp platba)
23. februára 2025, 6:36:33
23. marca 2025, 5:36:25
Web
Bonusové dvojdňové predplatné
2. augusta 2024, 23:02:05
4. augusta 2024, 23:02:05
Web
Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)
2. februára 2024, 22:02:39
2. augusta 2024, 23:01:52
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
2. októbra 2023, 18:47:41
30. októbra 2023, 18:47:41
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
22. júna 2023, 17:21:19
20. júla 2023, 17:21:19
Web
Bonusové dvojdňové predplatné
18. apríla 2023, 16:51:04
20. apríla 2023, 16:51:04
Web
Webové mini predplatné N / rok za 47,90 EUR
18. apríla 2022, 16:50:06
18. apríla 2023, 16:50:06
Web
Webové mini predplatné N / 4 týždne
21. marca 2022, 16:50:06
18. apríla 2022, 16:50:06
Web
Webové mini predplatné N / 4 týždne
19. februára 2022, 21:59:22
19. marca 2022, 21:59:22
Web
Webové mini predplatné N / 4 týždne
3. januára 2022, 10:46:12
31. januára 2022, 10:46:12
Web
Webové mini predplatné N / 4 týždne
3. decembra 2021, 16:12:07
31. decembra 2021, 16:12:07
Web
Webové mini predplatné N / mesiac
29. októbra 2021, 6:47:26
29. novembra 2021, 6:47:26
Web
Webové mini predplatné N / mesiac
1. augusta 2021, 8:51:45
1. septembra 2021, 8:51:45
Web
Webové mini predplatné N / mesiac
24. júna 2021, 9:26:56
25. júla 2021, 9:26:56
Web
Webové mini predplatné N / mesiac
23. mája 2021, 7:32:29
23. júna 2021, 7:32:29
Web
Webové mini predplatné N / mesiac
15. apríla 2021, 8:53:25
16. mája 2021, 8:53:25
Web
Webové mini predplatné N / mesiac
15. marca 2021, 8:53:25
15. apríla 2021, 8:53:25
Web
Webové mini predplatné N / mesiac
8. februára 2021, 4:12:51
11. marca 2021, 4:12:51
Web
Webové mini predplatné N / mesiac
8. januára 2021, 4:12:51
8. februára 2021, 4:12:51
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
16. septembra 2020, 13:18:28
17. októbra 2020, 13:18:28
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
16. augusta 2020, 8:37:44
16. septembra 2020, 8:37:44
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
11. júna 2020, 6:49:27
12. júla 2020, 6:49:27
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
30. marca 2020, 13:07:55
29. apríla 2020, 13:07:55
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
28. februára 2020, 13:07:55
30. marca 2020, 13:07:55
Web
Webové mini predplatné N / mesiac
20. marca 2019, 6:27:23
20. apríla 2019, 6:27:23
Web
Webové mini predplatné N / mesiac
8. januára 2019, 20:28:39
8. februára 2019, 20:28:39
Web
Webové mini predplatné N / mesiac
15. novembra 2018, 8:22:57
16. decembra 2018, 8:22:57
Web
Webové mini predplatné N / mesiac
2. októbra 2018, 7:36:23
2. novembra 2018, 7:36:23
Web
Webové mini predplatné N / mesiac
25. augusta 2018, 12:00:13
25. septembra 2018, 12:00:13
Web
Webové mini predplatné N / mesiac
10. mája 2018, 8:56:13
10. júna 2018, 8:56:13
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
2. apríla 2018, 8:35:31
3. mája 2018, 8:35:31
Web
Webové mini predplatné N / mesiac
27. februára 2018, 10:43:33
30. marca 2018, 10:43:33
Web
Webové mini predplatné N / mesiac
25. januára 2018, 8:57:38
25. februára 2018, 8:57:38
Web
Webové mini predplatné N / mesiac
5. apríla 2017, 9:38:35
6. mája 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
5. marca 2017, 9:38:35
5. apríla 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
2. februára 2017, 9:38:35
5. marca 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
2. januára 2017, 9:38:35
2. februára 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
30. novembra 2016, 11:50:08
31. decembra 2016, 11:50:08
Web
Webové mini predplatné N / mesiac
13. októbra 2016, 11:53:06
18. novembra 2016, 11:53:06
Web
Mesačné predplatné dennikn.sk
29. júla 2016, 12:10:10
29. augusta 2016, 12:10:10
Web
Mesačné predplatné dennikn.sk
25. mája 2016, 8:45:24
25. júna 2016, 8:45:24
Web
Mobilné aplikácie
Mobil zadarmo pri prvom prihlaseni
8. februára 2016, 15:54:30
22. februára 2016, 15:54:30
Web
Mesačné predplatné dennikn.sk
2. januára 2016, 12:11:00
2. februára 2016, 12:11:00
Web
Mesačné predplatné dennikn.sk
2. decembra 2015, 12:11:00
2. januára 2016, 12:11:00
Web
Mesačné predplatné dennikn.sk
1. novembra 2015, 12:11:00
2. decembra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
1. októbra 2015, 12:11:00
1. novembra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
31. augusta 2015, 12:11:00
1. októbra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
31. júla 2015, 12:11:00
31. augusta 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
30. júna 2015, 12:11:00
31. júla 2015, 12:11:00
Predplatné
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR
Web
Štandard
Mobilné aplikácie
Týždeň
Štandard Denník N a .týždeň 1 mesiac (v3)
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac za 7,99 (inapp platba)
Web
Bonusové dvojdňové predplatné
Web
Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
Web
Bonusové dvojdňové predplatné
Web
Webové mini predplatné N / rok za 47,90 EUR
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mobilné aplikácie
Mobil zadarmo pri prvom prihlaseni
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Od
7. mája 2026, 17:59:50
9. apríla 2026, 17:59:50
Aktuálne
12. marca 2026, 17:59:50
12. februára 2026, 17:59:50
15. januára 2026, 17:59:50
18. decembra 2025, 17:59:50
25. septembra 2025, 17:59:50
15. mája 2025, 19:52:30
23. februára 2025, 6:36:33
2. augusta 2024, 23:02:05
2. februára 2024, 22:02:39
2. októbra 2023, 18:47:41
22. júna 2023, 17:21:19
18. apríla 2023, 16:51:04
18. apríla 2022, 16:50:06
21. marca 2022, 16:50:06
19. februára 2022, 21:59:22
3. januára 2022, 10:46:12
3. decembra 2021, 16:12:07
29. októbra 2021, 6:47:26
1. augusta 2021, 8:51:45
24. júna 2021, 9:26:56
23. mája 2021, 7:32:29
15. apríla 2021, 8:53:25
15. marca 2021, 8:53:25
8. februára 2021, 4:12:51
8. januára 2021, 4:12:51
16. septembra 2020, 13:18:28
16. augusta 2020, 8:37:44
11. júna 2020, 6:49:27
30. marca 2020, 13:07:55
28. februára 2020, 13:07:55
20. marca 2019, 6:27:23
8. januára 2019, 20:28:39
15. novembra 2018, 8:22:57
2. októbra 2018, 7:36:23
25. augusta 2018, 12:00:13
10. mája 2018, 8:56:13
2. apríla 2018, 8:35:31
27. februára 2018, 10:43:33
25. januára 2018, 8:57:38
5. apríla 2017, 9:38:35
5. marca 2017, 9:38:35
2. februára 2017, 9:38:35
2. januára 2017, 9:38:35
30. novembra 2016, 11:50:08
13. októbra 2016, 11:53:06
29. júla 2016, 12:10:10
25. mája 2016, 8:45:24
8. februára 2016, 15:54:30
2. januára 2016, 12:11:00
2. decembra 2015, 12:11:00
1. novembra 2015, 12:11:00
1. októbra 2015, 12:11:00
31. augusta 2015, 12:11:00
31. júla 2015, 12:11:00
30. júna 2015, 12:11:00
Do
4. júna 2026, 17:59:50
7. mája 2026, 17:59:50
9. apríla 2026, 17:59:50
12. marca 2026, 17:59:50
12. februára 2026, 17:59:50
15. januára 2026, 17:59:50
18. decembra 2025, 17:59:50
15. júna 2025, 19:52:30
23. marca 2025, 5:36:25
4. augusta 2024, 23:02:05
2. augusta 2024, 23:01:52
30. októbra 2023, 18:47:41
20. júla 2023, 17:21:19
20. apríla 2023, 16:51:04
18. apríla 2023, 16:50:06
18. apríla 2022, 16:50:06
19. marca 2022, 21:59:22
31. januára 2022, 10:46:12
31. decembra 2021, 16:12:07
29. novembra 2021, 6:47:26
1. septembra 2021, 8:51:45
25. júla 2021, 9:26:56
23. júna 2021, 7:32:29
16. mája 2021, 8:53:25
15. apríla 2021, 8:53:25
11. marca 2021, 4:12:51
8. februára 2021, 4:12:51
17. októbra 2020, 13:18:28
16. septembra 2020, 8:37:44
12. júla 2020, 6:49:27
29. apríla 2020, 13:07:55
30. marca 2020, 13:07:55
20. apríla 2019, 6:27:23
8. februára 2019, 20:28:39
16. decembra 2018, 8:22:57
2. novembra 2018, 7:36:23
25. septembra 2018, 12:00:13
10. júna 2018, 8:56:13
3. mája 2018, 8:35:31
30. marca 2018, 10:43:33
25. februára 2018, 8:57:38
6. mája 2017, 9:38:35
5. apríla 2017, 9:38:35
5. marca 2017, 9:38:35
2. februára 2017, 9:38:35
31. decembra 2016, 11:50:08
18. novembra 2016, 11:53:06
29. augusta 2016, 12:10:10
25. júna 2016, 8:45:24
22. februára 2016, 15:54:30
2. februára 2016, 12:11:00
2. januára 2016, 12:11:00
2. decembra 2015, 12:11:00
1. novembra 2015, 12:11:00
1. októbra 2015, 12:11:00
31. augusta 2015, 12:11:00
31. júla 2015, 12:11:00
Kliknite sem
Kliknite sem
, ak máte otázku alebo potrebujete poradiť.
O Denníku N
O Denníku N
Redakcia
Redakcia
Inzercia
Inzercia
Hospodárenie
Hospodárenie
Diskusie a podujatia
Diskusie a podujatia
Logo
Logo
Kontakt...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.080784574,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Home | Hostinger","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home | Hostinger","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.03025266,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login – Nginx Proxy Manager","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login – Nginx Proxy Manager","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.05069814,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Screenpipe — Archive","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: archive.db","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.040724736,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SQLite Web: db.sqlite","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.03756649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.11469415,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DXP4800PLUS-B5F8","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.036901597,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Moje predplatné | Denník N","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.113696806,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Moje predplatné | Denník N","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.047041222,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.10139628,"top":0.32083002,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.34796488,"width":0.108211435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Preskočiť hlavnú navigáciu","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Preskočiť hlavnú navigáciu","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Denník N","depth":8,"bounds":{"left":0.113696806,"top":0.0518755,"width":0.062832445,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"Denník N","depth":9,"bounds":{"left":0.113696806,"top":0.0518755,"width":0.062832445,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Denník N","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"kovaliklukas@gmail.com","depth":8,"bounds":{"left":0.92852396,"top":0.0518755,"width":0.07147604,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"kovaliklukas@gmail.com","depth":9,"bounds":{"left":0.9338431,"top":0.06344773,"width":0.060837764,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Predplatné","depth":10,"bounds":{"left":0.39361703,"top":0.09417398,"width":0.032579787,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Predplatné","depth":11,"bounds":{"left":0.3962766,"top":0.10574621,"width":0.027260639,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platby","depth":10,"bounds":{"left":0.4261968,"top":0.09417398,"width":0.02144282,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platby","depth":11,"bounds":{"left":0.42918882,"top":0.10574621,"width":0.015791224,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Newslettre","depth":10,"bounds":{"left":0.4476396,"top":0.09417398,"width":0.03307846,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Newslettre","depth":11,"bounds":{"left":0.45063165,"top":0.10574621,"width":0.027426861,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Témy a autori","depth":10,"bounds":{"left":0.48071808,"top":0.09417398,"width":0.039727394,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Témy a autori","depth":11,"bounds":{"left":0.4837101,"top":0.10574621,"width":0.034075797,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Adresa, faktúry","depth":10,"bounds":{"left":0.52044547,"top":0.09417398,"width":0.044049203,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Adresa, faktúry","depth":11,"bounds":{"left":0.5234375,"top":0.10574621,"width":0.038397606,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Nastavenia","depth":10,"bounds":{"left":0.56449467,"top":0.09417398,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Nastavenia","depth":11,"bounds":{"left":0.5674867,"top":0.10574621,"width":0.027925532,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Odhlásiť","depth":10,"bounds":{"left":0.5980718,"top":0.09417398,"width":0.02642952,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Odhlásiť","depth":11,"bounds":{"left":0.60106385,"top":0.10574621,"width":0.020777926,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Predĺžiť predplatné","depth":11,"bounds":{"left":0.57546544,"top":0.13647246,"width":0.059840426,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.578125,"top":0.14884278,"width":0.005319149,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Predĺžiť predplatné","depth":12,"bounds":{"left":0.5834442,"top":0.14804469,"width":0.04920213,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":" Zmeniť predplatné / Upgrade","depth":11,"bounds":{"left":0.63663566,"top":0.13647246,"width":0.08344415,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"","depth":13,"bounds":{"left":0.6392952,"top":0.14884278,"width":0.0039893617,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Zmeniť predplatné / Upgrade","depth":12,"bounds":{"left":0.64328456,"top":0.14804469,"width":0.07413564,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"História predplatných","depth":8,"bounds":{"left":0.39361703,"top":0.20670392,"width":0.32646278,"height":0.03152434},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"História predplatných","depth":9,"bounds":{"left":0.39361703,"top":0.20510775,"width":0.11868351,"height":0.03471668},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Predplatné","depth":13,"bounds":{"left":0.3962766,"top":0.2697526,"width":0.024102394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Od","depth":13,"bounds":{"left":0.58710104,"top":0.2697526,"width":0.006482713,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Do","depth":13,"bounds":{"left":0.65791225,"top":0.2697526,"width":0.0063164895,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.3028731,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.3028731,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.3028731,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.30047885,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7. mája 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.30047885,"width":0.047539894,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. júna 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.30047885,"width":0.04637633,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.33240223,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.33240223,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.33240223,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.33000797,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9. apríla 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.33000797,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Aktuálne","depth":13,"bounds":{"left":0.58926195,"top":0.34836394,"width":0.01462766,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7. mája 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.33000797,"width":0.047539894,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.37789306,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.37789306,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.37789306,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.3754988,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. marca 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.3754988,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9. apríla 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.3754988,"width":0.049035903,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.40742218,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.40742218,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.40742218,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.40502793,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. februára 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.40502793,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. marca 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.40502793,"width":0.05319149,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.4369513,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.4369513,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.4369513,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.43455705,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. januára 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.43455705,"width":0.055352394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. februára 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.43455705,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.46648043,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.46648043,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.46648043,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.4640862,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. decembra 2025, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.4640862,"width":0.061170213,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. januára 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.4640862,"width":0.055518616,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.4960096,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.4960096,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.4960096,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR","depth":13,"bounds":{"left":0.3962766,"top":0.49361533,"width":0.17702793,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. septembra 2025, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.49361533,"width":0.0625,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. decembra 2025, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.49361533,"width":0.041888297,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.5415004,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.5415004,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.5415004,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"bounds":{"left":0.4664229,"top":0.5415004,"width":0.011968086,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard Denník N a .týždeň 1 mesiac (v3)","depth":13,"bounds":{"left":0.48038563,"top":0.53910613,"width":0.08959442,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. mája 2025, 19:52:30","depth":13,"bounds":{"left":0.58710104,"top":0.53910613,"width":0.050199468,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. júna 2025, 19:52:30","depth":13,"bounds":{"left":0.65791225,"top":0.53910613,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.57102954,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.57102954,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.57102954,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac za 7,99 (inapp platba)","depth":13,"bounds":{"left":0.46293217,"top":0.5686353,"width":0.11884973,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. februára 2025, 6:36:33","depth":13,"bounds":{"left":0.58710104,"top":0.5686353,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. marca 2025, 5:36:25","depth":13,"bounds":{"left":0.65791225,"top":0.5686353,"width":0.050531916,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.60055864,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bonusové dvojdňové predplatné","depth":13,"bounds":{"left":0.40774602,"top":0.5981644,"width":0.06865027,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. augusta 2024, 23:02:05","depth":13,"bounds":{"left":0.58710104,"top":0.5981644,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. augusta 2024, 23:02:05","depth":13,"bounds":{"left":0.65791225,"top":0.5981644,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.6300878,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)","depth":13,"bounds":{"left":0.40774602,"top":0.62769353,"width":0.1314827,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2024, 22:02:39","depth":13,"bounds":{"left":0.58710104,"top":0.62769353,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. augusta 2024, 23:01:52","depth":13,"bounds":{"left":0.65791225,"top":0.62769353,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.65961695,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne za 5,99 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.6572227,"width":0.10521942,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2023, 18:47:41","depth":13,"bounds":{"left":0.58710104,"top":0.6572227,"width":0.053523935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. októbra 2023, 18:47:41","depth":13,"bounds":{"left":0.65791225,"top":0.6572227,"width":0.05618351,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.68914604,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne za 5,99 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.6867518,"width":0.10521942,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22. júna 2023, 17:21:19","depth":13,"bounds":{"left":0.58710104,"top":0.6867518,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. júla 2023, 17:21:19","depth":13,"bounds":{"left":0.65791225,"top":0.6867518,"width":0.04737367,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.7186752,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bonusové dvojdňové predplatné","depth":13,"bounds":{"left":0.40774602,"top":0.71628094,"width":0.06865027,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2023, 16:51:04","depth":13,"bounds":{"left":0.58710104,"top":0.71628094,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. apríla 2023, 16:51:04","depth":13,"bounds":{"left":0.65791225,"top":0.71628094,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.7482043,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / rok za 47,90 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.74581003,"width":0.0965758,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2022, 16:50:06","depth":13,"bounds":{"left":0.58710104,"top":0.74581003,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2023, 16:50:06","depth":13,"bounds":{"left":0.65791225,"top":0.74581003,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.77773345,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.7753392,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21. marca 2022, 16:50:06","depth":13,"bounds":{"left":0.58710104,"top":0.7753392,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2022, 16:50:06","depth":13,"bounds":{"left":0.65791225,"top":0.7753392,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.8072626,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.80486834,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19. februára 2022, 21:59:22","depth":13,"bounds":{"left":0.58710104,"top":0.80486834,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19. marca 2022, 21:59:22","depth":13,"bounds":{"left":0.65791225,"top":0.80486834,"width":0.05319149,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.8367917,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.83439744,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. januára 2022, 10:46:12","depth":13,"bounds":{"left":0.58710104,"top":0.83439744,"width":0.05285904,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. januára 2022, 10:46:12","depth":13,"bounds":{"left":0.65791225,"top":0.83439744,"width":0.055518616,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.86632085,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.8639266,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. decembra 2021, 16:12:07","depth":13,"bounds":{"left":0.58710104,"top":0.8639266,"width":0.05851064,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. decembra 2021, 16:12:07","depth":13,"bounds":{"left":0.65791225,"top":0.8639266,"width":0.041888297,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.91181165,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.9094174,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. októbra 2021, 6:47:26","depth":13,"bounds":{"left":0.58710104,"top":0.9094174,"width":0.053523935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. novembra 2021, 6:47:26","depth":13,"bounds":{"left":0.65791225,"top":0.9094174,"width":0.05851064,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.9413408,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.93894655,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. augusta 2021, 8:51:45","depth":13,"bounds":{"left":0.58710104,"top":0.93894655,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. septembra 2021, 8:51:45","depth":13,"bounds":{"left":0.65791225,"top":0.93894655,"width":0.057347074,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.9708699,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.96847564,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"24. júna 2021, 9:26:56","depth":13,"bounds":{"left":0.58710104,"top":0.96847564,"width":0.046210106,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. júla 2021, 9:26:56","depth":13,"bounds":{"left":0.65791225,"top":0.96847564,"width":0.044714097,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.0003989935},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.9980048,"width":0.0752992,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. mája 2021, 7:32:29","depth":13,"bounds":{"left":0.58710104,"top":0.9980048,"width":0.047539894,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. júna 2021, 7:32:29","depth":13,"bounds":{"left":0.65791225,"top":0.9980048,"width":0.04637633,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.029928207},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. apríla 2021, 8:53:25","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.04886968,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. mája 2021, 8:53:25","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.047539894,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.059457302},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. marca 2021, 8:53:25","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.050531916,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. apríla 2021, 8:53:25","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.049035903,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.0889864},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2021, 4:12:51","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.05236037,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11. marca 2021, 4:12:51","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.050531916,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2021, 4:12:51","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2021, 4:12:51","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. septembra 2020, 13:18:28","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17. októbra 2020, 13:18:28","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. augusta 2020, 8:37:44","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. septembra 2020, 8:37:44","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11. júna 2020, 6:49:27","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. júla 2020, 6:49:27","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. apríla 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28. februára 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. marca 2019, 6:27:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. apríla 2019, 6:27:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2019, 20:28:39","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2019, 20:28:39","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. novembra 2018, 8:22:57","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. decembra 2018, 8:22:57","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2018, 7:36:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. novembra 2018, 7:36:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. augusta 2018, 12:00:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. septembra 2018, 12:00:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. mája 2018, 8:56:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. júna 2018, 8:56:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. apríla 2018, 8:35:31","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. mája 2018, 8:35:31","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27. februára 2018, 10:43:33","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2018, 10:43:33","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. januára 2018, 8:57:38","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. februára 2018, 8:57:38","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. apríla 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6. mája 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. marca 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. apríla 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. marca 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. novembra 2016, 11:50:08","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. decembra 2016, 11:50:08","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13. októbra 2016, 11:53:06","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. novembra 2016, 11:53:06","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. júla 2016, 12:10:10","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. augusta 2016, 12:10:10","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. mája 2016, 8:45:24","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. júna 2016, 8:45:24","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobil zadarmo pri prvom prihlaseni","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2016, 15:54:30","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22. februára 2016, 15:54:30","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. decembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. novembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. decembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. októbra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. novembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. augusta 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. októbra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. júla 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. augusta 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. júna 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. júla 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Predplatné","depth":13,"bounds":{"left":0.3962766,"top":0.2697526,"width":0.024102394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.3028731,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.3028731,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.3028731,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.30047885,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.33240223,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.33240223,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.33240223,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.33000797,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.37789306,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.37789306,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.37789306,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / 4 týždne za 7,99 EUR","depth":13,"bounds":{"left":0.46293217,"top":0.3754988,"width":0.102726065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.40742218,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.40742218,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.40742218,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.40502793,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.4369513,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.4369513,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.4369513,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.43455705,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.46648043,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.46648043,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.46648043,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard predplatné na 4 týždne za polovičnú cenu","depth":13,"bounds":{"left":0.46293217,"top":0.4640862,"width":0.10837766,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.4960096,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.4960096,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.4960096,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR","depth":13,"bounds":{"left":0.3962766,"top":0.49361533,"width":0.17702793,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.5415004,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.5415004,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.5415004,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"bounds":{"left":0.4664229,"top":0.5415004,"width":0.011968086,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard Denník N a .týždeň 1 mesiac (v3)","depth":13,"bounds":{"left":0.48038563,"top":0.53910613,"width":0.08959442,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.57102954,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"bounds":{"left":0.41107047,"top":0.57102954,"width":0.01512633,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"bounds":{"left":0.43168217,"top":0.57102954,"width":0.02925532,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac za 7,99 (inapp platba)","depth":13,"bounds":{"left":0.46293217,"top":0.5686353,"width":0.11884973,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.60055864,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bonusové dvojdňové predplatné","depth":13,"bounds":{"left":0.40774602,"top":0.5981644,"width":0.06865027,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.6300878,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)","depth":13,"bounds":{"left":0.40774602,"top":0.62769353,"width":0.1314827,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.65961695,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne za 5,99 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.6572227,"width":0.10521942,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.68914604,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne za 5,99 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.6867518,"width":0.10521942,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.7186752,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bonusové dvojdňové predplatné","depth":13,"bounds":{"left":0.40774602,"top":0.71628094,"width":0.06865027,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.7482043,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / rok za 47,90 EUR","depth":13,"bounds":{"left":0.40774602,"top":0.74581003,"width":0.0965758,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.77773345,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.7753392,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.8072626,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.80486834,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.8367917,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.83439744,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.86632085,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / 4 týždne","depth":13,"bounds":{"left":0.40774602,"top":0.8639266,"width":0.078125,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.91181165,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.9094174,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.9413408,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.93894655,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":0.9708699,"width":0.0071476065,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.96847564,"width":0.0752992,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.0003989935},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":0.9980048,"width":0.0752992,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.029928207},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.059457302},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"bounds":{"left":0.3984375,"top":1.0,"width":0.0071476065,"height":-0.0889864},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"bounds":{"left":0.40774602,"top":1.0,"width":0.0752992,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandard","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Štandardné predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Týždeň","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk a tyzden.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Webové mini predplatné N / mesiac","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobilné aplikácie","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mobil zadarmo pri prvom prihlaseni","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Web","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mesačné predplatné dennikn.sk","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Od","depth":13,"bounds":{"left":0.58710104,"top":0.2697526,"width":0.006482713,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7. mája 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.30047885,"width":0.047539894,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9. apríla 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.33000797,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Aktuálne","depth":13,"bounds":{"left":0.58926195,"top":0.34836394,"width":0.01462766,"height":0.0103751},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. marca 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.3754988,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. februára 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.40502793,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. januára 2026, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.43455705,"width":0.055352394,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. decembra 2025, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.4640862,"width":0.061170213,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. septembra 2025, 17:59:50","depth":13,"bounds":{"left":0.58710104,"top":0.49361533,"width":0.0625,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. mája 2025, 19:52:30","depth":13,"bounds":{"left":0.58710104,"top":0.53910613,"width":0.050199468,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. februára 2025, 6:36:33","depth":13,"bounds":{"left":0.58710104,"top":0.5686353,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. augusta 2024, 23:02:05","depth":13,"bounds":{"left":0.58710104,"top":0.5981644,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2024, 22:02:39","depth":13,"bounds":{"left":0.58710104,"top":0.62769353,"width":0.054853722,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2023, 18:47:41","depth":13,"bounds":{"left":0.58710104,"top":0.6572227,"width":0.053523935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22. júna 2023, 17:21:19","depth":13,"bounds":{"left":0.58710104,"top":0.6867518,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2023, 16:51:04","depth":13,"bounds":{"left":0.58710104,"top":0.71628094,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2022, 16:50:06","depth":13,"bounds":{"left":0.58710104,"top":0.74581003,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21. marca 2022, 16:50:06","depth":13,"bounds":{"left":0.58710104,"top":0.7753392,"width":0.053025264,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19. februára 2022, 21:59:22","depth":13,"bounds":{"left":0.58710104,"top":0.80486834,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. januára 2022, 10:46:12","depth":13,"bounds":{"left":0.58710104,"top":0.83439744,"width":0.05285904,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. decembra 2021, 16:12:07","depth":13,"bounds":{"left":0.58710104,"top":0.8639266,"width":0.05851064,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. októbra 2021, 6:47:26","depth":13,"bounds":{"left":0.58710104,"top":0.9094174,"width":0.053523935,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. augusta 2021, 8:51:45","depth":13,"bounds":{"left":0.58710104,"top":0.93894655,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"24. júna 2021, 9:26:56","depth":13,"bounds":{"left":0.58710104,"top":0.96847564,"width":0.046210106,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. mája 2021, 7:32:29","depth":13,"bounds":{"left":0.58710104,"top":0.9980048,"width":0.047539894,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. apríla 2021, 8:53:25","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.04886968,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. marca 2021, 8:53:25","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.050531916,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2021, 4:12:51","depth":13,"bounds":{"left":0.58710104,"top":1.0,"width":0.05236037,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2021, 4:12:51","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. septembra 2020, 13:18:28","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. augusta 2020, 8:37:44","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11. júna 2020, 6:49:27","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"28. februára 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. marca 2019, 6:27:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. januára 2019, 20:28:39","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. novembra 2018, 8:22:57","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. októbra 2018, 7:36:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. augusta 2018, 12:00:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. mája 2018, 8:56:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. apríla 2018, 8:35:31","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"27. februára 2018, 10:43:33","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. januára 2018, 8:57:38","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. apríla 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. marca 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. novembra 2016, 11:50:08","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13. októbra 2016, 11:53:06","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. júla 2016, 12:10:10","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. mája 2016, 8:45:24","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2016, 15:54:30","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. decembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. novembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. októbra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. augusta 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. júla 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. júna 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Do","depth":13,"bounds":{"left":0.65791225,"top":0.2697526,"width":0.0063164895,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. júna 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.30047885,"width":0.04637633,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7. mája 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.33000797,"width":0.047539894,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9. apríla 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.3754988,"width":0.049035903,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. marca 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.40502793,"width":0.05319149,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. februára 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.43455705,"width":0.057513297,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. januára 2026, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.4640862,"width":0.055518616,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. decembra 2025, 17:59:50","depth":13,"bounds":{"left":0.65791225,"top":0.49361533,"width":0.041888297,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. júna 2025, 19:52:30","depth":13,"bounds":{"left":0.65791225,"top":0.53910613,"width":0.04886968,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. marca 2025, 5:36:25","depth":13,"bounds":{"left":0.65791225,"top":0.5686353,"width":0.050531916,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4. augusta 2024, 23:02:05","depth":13,"bounds":{"left":0.65791225,"top":0.5981644,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. augusta 2024, 23:01:52","depth":13,"bounds":{"left":0.65791225,"top":0.62769353,"width":0.05418883,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. októbra 2023, 18:47:41","depth":13,"bounds":{"left":0.65791225,"top":0.6572227,"width":0.05618351,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. júla 2023, 17:21:19","depth":13,"bounds":{"left":0.65791225,"top":0.6867518,"width":0.04737367,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. apríla 2023, 16:51:04","depth":13,"bounds":{"left":0.65791225,"top":0.71628094,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2023, 16:50:06","depth":13,"bounds":{"left":0.65791225,"top":0.74581003,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. apríla 2022, 16:50:06","depth":13,"bounds":{"left":0.65791225,"top":0.7753392,"width":0.051529255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19. marca 2022, 21:59:22","depth":13,"bounds":{"left":0.65791225,"top":0.80486834,"width":0.05319149,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. januára 2022, 10:46:12","depth":13,"bounds":{"left":0.65791225,"top":0.83439744,"width":0.055518616,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. decembra 2021, 16:12:07","depth":13,"bounds":{"left":0.65791225,"top":0.8639266,"width":0.041888297,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. novembra 2021, 6:47:26","depth":13,"bounds":{"left":0.65791225,"top":0.9094174,"width":0.05851064,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. septembra 2021, 8:51:45","depth":13,"bounds":{"left":0.65791225,"top":0.93894655,"width":0.057347074,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. júla 2021, 9:26:56","depth":13,"bounds":{"left":0.65791225,"top":0.96847564,"width":0.044714097,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23. júna 2021, 7:32:29","depth":13,"bounds":{"left":0.65791225,"top":0.9980048,"width":0.04637633,"height":0.0019952059},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. mája 2021, 8:53:25","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.047539894,"height":-0.027533889},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15. apríla 2021, 8:53:25","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.049035903,"height":-0.057063103},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11. marca 2021, 4:12:51","depth":13,"bounds":{"left":0.65791225,"top":1.0,"width":0.050531916,"height":-0.0865922},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2021, 4:12:51","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17. októbra 2020, 13:18:28","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. septembra 2020, 8:37:44","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12. júla 2020, 6:49:27","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. apríla 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2020, 13:07:55","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20. apríla 2019, 6:27:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8. februára 2019, 20:28:39","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16. decembra 2018, 8:22:57","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. novembra 2018, 7:36:23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. septembra 2018, 12:00:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10. júna 2018, 8:56:13","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3. mája 2018, 8:35:31","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"30. marca 2018, 10:43:33","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. februára 2018, 8:57:38","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6. mája 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. apríla 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5. marca 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2017, 9:38:35","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. decembra 2016, 11:50:08","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18. novembra 2016, 11:53:06","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"29. augusta 2016, 12:10:10","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"25. júna 2016, 8:45:24","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22. februára 2016, 15:54:30","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. februára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. januára 2016, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2. decembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. novembra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1. októbra 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. augusta 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"31. júla 2015, 12:11:00","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kliknite sem","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Kliknite sem","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", ak máte otázku alebo potrebujete poradiť.","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"O Denníku N","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"O Denníku N","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Redakcia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Redakcia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inzercia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inzercia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Hospodárenie","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Hospodárenie","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Diskusie a podujatia","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Diskusie a podujatia","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Logo","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Logo","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Kontakt","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8934334874883521545
|
-8637956591292447896
|
visual_change
|
accessibility
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
Home | Hostinger
Home | Hostinger
Login – Nginx Proxy Manager
Login – Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
Moje predplatné | Denník N
Moje predplatné | Denník N
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Preskočiť hlavnú navigáciu
Preskočiť hlavnú navigáciu
Denník N
Denník N
Denník N
[EMAIL]
[EMAIL]
Predplatné
Predplatné
Platby
Platby
Newslettre
Newslettre
Témy a autori
Témy a autori
Adresa, faktúry
Adresa, faktúry
Nastavenia
Nastavenia
Odhlásiť
Odhlásiť
Predĺžiť predplatné
Predĺžiť predplatné
Zmeniť predplatné / Upgrade
Zmeniť predplatné / Upgrade
História predplatných
História predplatných
Predplatné
Od
Do
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
7. mája 2026, 17:59:50
4. júna 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
9. apríla 2026, 17:59:50
Aktuálne
7. mája 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
12. marca 2026, 17:59:50
9. apríla 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
12. februára 2026, 17:59:50
12. marca 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
15. januára 2026, 17:59:50
12. februára 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
18. decembra 2025, 17:59:50
15. januára 2026, 17:59:50
Web
Štandard
Mobilné aplikácie
Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR
25. septembra 2025, 17:59:50
18. decembra 2025, 17:59:50
Web
Štandard
Mobilné aplikácie
Týždeň
Štandard Denník N a .týždeň 1 mesiac (v3)
15. mája 2025, 19:52:30
15. júna 2025, 19:52:30
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac za 7,99 (inapp platba)
23. februára 2025, 6:36:33
23. marca 2025, 5:36:25
Web
Bonusové dvojdňové predplatné
2. augusta 2024, 23:02:05
4. augusta 2024, 23:02:05
Web
Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)
2. februára 2024, 22:02:39
2. augusta 2024, 23:01:52
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
2. októbra 2023, 18:47:41
30. októbra 2023, 18:47:41
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
22. júna 2023, 17:21:19
20. júla 2023, 17:21:19
Web
Bonusové dvojdňové predplatné
18. apríla 2023, 16:51:04
20. apríla 2023, 16:51:04
Web
Webové mini predplatné N / rok za 47,90 EUR
18. apríla 2022, 16:50:06
18. apríla 2023, 16:50:06
Web
Webové mini predplatné N / 4 týždne
21. marca 2022, 16:50:06
18. apríla 2022, 16:50:06
Web
Webové mini predplatné N / 4 týždne
19. februára 2022, 21:59:22
19. marca 2022, 21:59:22
Web
Webové mini predplatné N / 4 týždne
3. januára 2022, 10:46:12
31. januára 2022, 10:46:12
Web
Webové mini predplatné N / 4 týždne
3. decembra 2021, 16:12:07
31. decembra 2021, 16:12:07
Web
Webové mini predplatné N / mesiac
29. októbra 2021, 6:47:26
29. novembra 2021, 6:47:26
Web
Webové mini predplatné N / mesiac
1. augusta 2021, 8:51:45
1. septembra 2021, 8:51:45
Web
Webové mini predplatné N / mesiac
24. júna 2021, 9:26:56
25. júla 2021, 9:26:56
Web
Webové mini predplatné N / mesiac
23. mája 2021, 7:32:29
23. júna 2021, 7:32:29
Web
Webové mini predplatné N / mesiac
15. apríla 2021, 8:53:25
16. mája 2021, 8:53:25
Web
Webové mini predplatné N / mesiac
15. marca 2021, 8:53:25
15. apríla 2021, 8:53:25
Web
Webové mini predplatné N / mesiac
8. februára 2021, 4:12:51
11. marca 2021, 4:12:51
Web
Webové mini predplatné N / mesiac
8. januára 2021, 4:12:51
8. februára 2021, 4:12:51
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
16. septembra 2020, 13:18:28
17. októbra 2020, 13:18:28
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
16. augusta 2020, 8:37:44
16. septembra 2020, 8:37:44
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
11. júna 2020, 6:49:27
12. júla 2020, 6:49:27
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
30. marca 2020, 13:07:55
29. apríla 2020, 13:07:55
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
28. februára 2020, 13:07:55
30. marca 2020, 13:07:55
Web
Webové mini predplatné N / mesiac
20. marca 2019, 6:27:23
20. apríla 2019, 6:27:23
Web
Webové mini predplatné N / mesiac
8. januára 2019, 20:28:39
8. februára 2019, 20:28:39
Web
Webové mini predplatné N / mesiac
15. novembra 2018, 8:22:57
16. decembra 2018, 8:22:57
Web
Webové mini predplatné N / mesiac
2. októbra 2018, 7:36:23
2. novembra 2018, 7:36:23
Web
Webové mini predplatné N / mesiac
25. augusta 2018, 12:00:13
25. septembra 2018, 12:00:13
Web
Webové mini predplatné N / mesiac
10. mája 2018, 8:56:13
10. júna 2018, 8:56:13
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
2. apríla 2018, 8:35:31
3. mája 2018, 8:35:31
Web
Webové mini predplatné N / mesiac
27. februára 2018, 10:43:33
30. marca 2018, 10:43:33
Web
Webové mini predplatné N / mesiac
25. januára 2018, 8:57:38
25. februára 2018, 8:57:38
Web
Webové mini predplatné N / mesiac
5. apríla 2017, 9:38:35
6. mája 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
5. marca 2017, 9:38:35
5. apríla 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
2. februára 2017, 9:38:35
5. marca 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
2. januára 2017, 9:38:35
2. februára 2017, 9:38:35
Web
Webové mini predplatné N / mesiac
30. novembra 2016, 11:50:08
31. decembra 2016, 11:50:08
Web
Webové mini predplatné N / mesiac
13. októbra 2016, 11:53:06
18. novembra 2016, 11:53:06
Web
Mesačné predplatné dennikn.sk
29. júla 2016, 12:10:10
29. augusta 2016, 12:10:10
Web
Mesačné predplatné dennikn.sk
25. mája 2016, 8:45:24
25. júna 2016, 8:45:24
Web
Mobilné aplikácie
Mobil zadarmo pri prvom prihlaseni
8. februára 2016, 15:54:30
22. februára 2016, 15:54:30
Web
Mesačné predplatné dennikn.sk
2. januára 2016, 12:11:00
2. februára 2016, 12:11:00
Web
Mesačné predplatné dennikn.sk
2. decembra 2015, 12:11:00
2. januára 2016, 12:11:00
Web
Mesačné predplatné dennikn.sk
1. novembra 2015, 12:11:00
2. decembra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
1. októbra 2015, 12:11:00
1. novembra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
31. augusta 2015, 12:11:00
1. októbra 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
31. júla 2015, 12:11:00
31. augusta 2015, 12:11:00
Web
Mesačné predplatné dennikn.sk
30. júna 2015, 12:11:00
31. júla 2015, 12:11:00
Predplatné
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / 4 týždne za 7,99 EUR
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Štandard predplatné na 4 týždne za polovičnú cenu
Web
Štandard
Mobilné aplikácie
Akciové štandard predplatné N 2025 / 12 týždňov za 1,00 EUR
Web
Štandard
Mobilné aplikácie
Týždeň
Štandard Denník N a .týždeň 1 mesiac (v3)
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac za 7,99 (inapp platba)
Web
Bonusové dvojdňové predplatné
Web
Mini N / 6 mesiacov so 6 € trial zľavou (Subscribe with Google)
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
Web
Webové mini predplatné N / 4 týždne za 5,99 EUR
Web
Bonusové dvojdňové predplatné
Web
Webové mini predplatné N / rok za 47,90 EUR
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / 4 týždne
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Štandard
Mobilné aplikácie
Štandardné predplatné N / mesiac
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Týždeň
Mesačné predplatné dennikn.sk a tyzden.sk
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Webové mini predplatné N / mesiac
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mobilné aplikácie
Mobil zadarmo pri prvom prihlaseni
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Web
Mesačné predplatné dennikn.sk
Od
7. mája 2026, 17:59:50
9. apríla 2026, 17:59:50
Aktuálne
12. marca 2026, 17:59:50
12. februára 2026, 17:59:50
15. januára 2026, 17:59:50
18. decembra 2025, 17:59:50
25. septembra 2025, 17:59:50
15. mája 2025, 19:52:30
23. februára 2025, 6:36:33
2. augusta 2024, 23:02:05
2. februára 2024, 22:02:39
2. októbra 2023, 18:47:41
22. júna 2023, 17:21:19
18. apríla 2023, 16:51:04
18. apríla 2022, 16:50:06
21. marca 2022, 16:50:06
19. februára 2022, 21:59:22
3. januára 2022, 10:46:12
3. decembra 2021, 16:12:07
29. októbra 2021, 6:47:26
1. augusta 2021, 8:51:45
24. júna 2021, 9:26:56
23. mája 2021, 7:32:29
15. apríla 2021, 8:53:25
15. marca 2021, 8:53:25
8. februára 2021, 4:12:51
8. januára 2021, 4:12:51
16. septembra 2020, 13:18:28
16. augusta 2020, 8:37:44
11. júna 2020, 6:49:27
30. marca 2020, 13:07:55
28. februára 2020, 13:07:55
20. marca 2019, 6:27:23
8. januára 2019, 20:28:39
15. novembra 2018, 8:22:57
2. októbra 2018, 7:36:23
25. augusta 2018, 12:00:13
10. mája 2018, 8:56:13
2. apríla 2018, 8:35:31
27. februára 2018, 10:43:33
25. januára 2018, 8:57:38
5. apríla 2017, 9:38:35
5. marca 2017, 9:38:35
2. februára 2017, 9:38:35
2. januára 2017, 9:38:35
30. novembra 2016, 11:50:08
13. októbra 2016, 11:53:06
29. júla 2016, 12:10:10
25. mája 2016, 8:45:24
8. februára 2016, 15:54:30
2. januára 2016, 12:11:00
2. decembra 2015, 12:11:00
1. novembra 2015, 12:11:00
1. októbra 2015, 12:11:00
31. augusta 2015, 12:11:00
31. júla 2015, 12:11:00
30. júna 2015, 12:11:00
Do
4. júna 2026, 17:59:50
7. mája 2026, 17:59:50
9. apríla 2026, 17:59:50
12. marca 2026, 17:59:50
12. februára 2026, 17:59:50
15. januára 2026, 17:59:50
18. decembra 2025, 17:59:50
15. júna 2025, 19:52:30
23. marca 2025, 5:36:25
4. augusta 2024, 23:02:05
2. augusta 2024, 23:01:52
30. októbra 2023, 18:47:41
20. júla 2023, 17:21:19
20. apríla 2023, 16:51:04
18. apríla 2023, 16:50:06
18. apríla 2022, 16:50:06
19. marca 2022, 21:59:22
31. januára 2022, 10:46:12
31. decembra 2021, 16:12:07
29. novembra 2021, 6:47:26
1. septembra 2021, 8:51:45
25. júla 2021, 9:26:56
23. júna 2021, 7:32:29
16. mája 2021, 8:53:25
15. apríla 2021, 8:53:25
11. marca 2021, 4:12:51
8. februára 2021, 4:12:51
17. októbra 2020, 13:18:28
16. septembra 2020, 8:37:44
12. júla 2020, 6:49:27
29. apríla 2020, 13:07:55
30. marca 2020, 13:07:55
20. apríla 2019, 6:27:23
8. februára 2019, 20:28:39
16. decembra 2018, 8:22:57
2. novembra 2018, 7:36:23
25. septembra 2018, 12:00:13
10. júna 2018, 8:56:13
3. mája 2018, 8:35:31
30. marca 2018, 10:43:33
25. februára 2018, 8:57:38
6. mája 2017, 9:38:35
5. apríla 2017, 9:38:35
5. marca 2017, 9:38:35
2. februára 2017, 9:38:35
31. decembra 2016, 11:50:08
18. novembra 2016, 11:53:06
29. augusta 2016, 12:10:10
25. júna 2016, 8:45:24
22. februára 2016, 15:54:30
2. februára 2016, 12:11:00
2. januára 2016, 12:11:00
2. decembra 2015, 12:11:00
1. novembra 2015, 12:11:00
1. októbra 2015, 12:11:00
31. augusta 2015, 12:11:00
31. júla 2015, 12:11:00
Kliknite sem
Kliknite sem
, ak máte otázku alebo potrebujete poradiť.
O Denníku N
O Denníku N
Redakcia
Redakcia
Inzercia
Inzercia
Hospodárenie
Hospodárenie
Diskusie a podujatia
Diskusie a podujatia
Logo
Logo
Kontakt...
|
611
|
NULL
|
NULL
|
NULL
|
|
6200
|
253
|
8
|
2026-05-07T17:53:36.620909+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778176416620_m1.jpg...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master<br/>Some incoming commits are not fetched<br/>","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\Utility\\Service;\n\nuse Illuminate\\Cache\\RateLimiter;\nuse Jiminny\\Contracts\\Http\\RateLimited;\nuse Jiminny\\Contracts\\Http\\RateLimitInterface;\n\nclass ProviderRateLimiter\n{\n protected RateLimiter $rateLimiter;\n\n public function __construct(RateLimiter $rateLimiter)\n {\n $this->rateLimiter = $rateLimiter;\n }\n\n public function canMakeRequest(RateLimited $provider): bool\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $key = $rateLimit->getKey();\n\n if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {\n return false;\n }\n }\n\n return true;\n }\n\n public function requestAvailableIn(RateLimited $provider): int\n {\n return $provider->getRateLimits()->isNotEmpty()\n ? $provider->getRateLimits()\n ->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))\n ->max()\n : 0\n ;\n }\n\n public function incrementRequestCount(RateLimited $provider): void\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\Utility\\Service;\n\nuse Illuminate\\Cache\\RateLimiter;\nuse Jiminny\\Contracts\\Http\\RateLimited;\nuse Jiminny\\Contracts\\Http\\RateLimitInterface;\n\nclass ProviderRateLimiter\n{\n protected RateLimiter $rateLimiter;\n\n public function __construct(RateLimiter $rateLimiter)\n {\n $this->rateLimiter = $rateLimiter;\n }\n\n public function canMakeRequest(RateLimited $provider): bool\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $key = $rateLimit->getKey();\n\n if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {\n return false;\n }\n }\n\n return true;\n }\n\n public function requestAvailableIn(RateLimited $provider): int\n {\n return $provider->getRateLimits()->isNotEmpty()\n ? $provider->getRateLimits()\n ->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))\n ->max()\n : 0\n ;\n }\n\n public function incrementRequestCount(RateLimited $provider): void\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Connection;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmActivityService;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\n\nclass MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n public int $tries = 3;\n\n private int $activityId;\n private ?Configuration $fromConfiguration;\n private bool $remoteSearch;\n\n public function middleware(): array\n {\n return [new HandleHubspotRateLimit()];\n }\n\n public function __construct(\n int $activityId,\n ?Configuration $fromConfiguration = null,\n bool $remoteSearch = false,\n ) {\n $this->activityId = $activityId;\n $this->fromConfiguration = $fromConfiguration;\n $this->remoteSearch = $remoteSearch;\n\n $this->onQueue(Constants::QUEUE_ANALYTICS_LOW);\n }\n\n public function uniqueId(): string\n {\n $configId = $this->fromConfiguration?->getId() ?? 0;\n $remote = $this->remoteSearch ? 'remote' : 'local';\n\n return \"$this->activityId:$configId:$remote\";\n }\n\n public function timeout(): int\n {\n return 300; // 5 minutes max execution time\n }\n\n public function uniqueFor(): int\n {\n return $this->timeout() + 60; // timeout + 1 minute buffer\n }\n\n public function backoff(): array\n {\n return [30, 90, 180];\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws Exception|Throwable\n */\n public function handle(\n ActivityRepository $activityRepository,\n CrmActivityService $crmActivityService,\n Connection $connection,\n ): void {\n $activity = $activityRepository->findById($this->activityId);\n if ($activity === null) {\n throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');\n }\n\n try {\n $connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {\n Log::info('[MatchActivityCrmData] Starting CRM data matching', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'set_configuration' => $this->fromConfiguration?->getId(),\n 'old_state' => [\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ],\n ]);\n\n $this->resetCrmMappings($activity, $activityRepository);\n\n $this->switchCrmConfigurationIfNeeded($activity);\n\n $activity->refresh();\n\n $crmActivityService->updateCrmData(\n activity: $activity,\n remoteSearch: $this->remoteSearch,\n );\n\n $hasMatch = $activity->getLead() !== null\n || $activity->getContact() !== null\n || $activity->getAccount() !== null\n || $activity->getOpportunity() !== null;\n\n if ($hasMatch) {\n Log::info('[MatchActivityCrmData] Successfully matched CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ]);\n } else {\n Log::info('[MatchActivityCrmData] No CRM match found', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n ]);\n }\n });\n } catch (Throwable $e) {\n Log::error('[MatchActivityCrmData] Failed to match CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'exception' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n\n throw $e;\n }\n }\n\n public function failed(Throwable $exception): void\n {\n Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'from_configuration' => $this->fromConfiguration?->getId(),\n 'exception' => $exception->getMessage(),\n 'attempts' => $this->attempts(),\n ]);\n }\n\n private function resetCrmMappings(\n Activity $activity,\n ActivityRepository $activityRepository\n ): void {\n $activity->update([\n 'lead_id' => null,\n 'contact_id' => null,\n 'account_id' => null,\n 'opportunity_id' => null,\n 'stage_id' => null,\n ]);\n\n $participantsOldState = $activityRepository->getActivityParticipants($activity)\n ->map(function ($participant) {\n return [\n 'id' => $participant->id,\n 'user_id' => $participant->user_id,\n 'contact_id' => $participant->contact_id,\n 'lead_id' => $participant->lead_id,\n ];\n });\n\n if ($participantsOldState->isNotEmpty()) {\n Log::info('[MatchActivityCrmData] Participants old state', [\n 'activity' => $this->activityId,\n 'participants' => $participantsOldState->toArray(),\n ]);\n }\n\n $activity->participants()->update([\n 'user_id' => null,\n 'contact_id' => null,\n 'lead_id' => null,\n ]);\n }\n\n private function switchCrmConfigurationIfNeeded(Activity $activity): void\n {\n if ($this->fromConfiguration === null) {\n return;\n }\n\n if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {\n return;\n }\n\n Log::info('[MatchActivityCrmData] Switching CRM configuration', [\n 'activity' => $this->activityId,\n 'old_configuration' => $activity->getCrm()?->getId(),\n 'new_configuration' => $this->fromConfiguration->getId(),\n ]);\n\n $activity->update([\n 'crm_configuration_id' => $this->fromConfiguration->getId(),\n 'crm_provider_id' => null,\n ]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Connection;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmActivityService;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\n\nclass MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n public int $tries = 3;\n\n private int $activityId;\n private ?Configuration $fromConfiguration;\n private bool $remoteSearch;\n\n public function middleware(): array\n {\n return [new HandleHubspotRateLimit()];\n }\n\n public function __construct(\n int $activityId,\n ?Configuration $fromConfiguration = null,\n bool $remoteSearch = false,\n ) {\n $this->activityId = $activityId;\n $this->fromConfiguration = $fromConfiguration;\n $this->remoteSearch = $remoteSearch;\n\n $this->onQueue(Constants::QUEUE_ANALYTICS_LOW);\n }\n\n public function uniqueId(): string\n {\n $configId = $this->fromConfiguration?->getId() ?? 0;\n $remote = $this->remoteSearch ? 'remote' : 'local';\n\n return \"$this->activityId:$configId:$remote\";\n }\n\n public function timeout(): int\n {\n return 300; // 5 minutes max execution time\n }\n\n public function uniqueFor(): int\n {\n return $this->timeout() + 60; // timeout + 1 minute buffer\n }\n\n public function backoff(): array\n {\n return [30, 90, 180];\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws Exception|Throwable\n */\n public function handle(\n ActivityRepository $activityRepository,\n CrmActivityService $crmActivityService,\n Connection $connection,\n ): void {\n $activity = $activityRepository->findById($this->activityId);\n if ($activity === null) {\n throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');\n }\n\n try {\n $connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {\n Log::info('[MatchActivityCrmData] Starting CRM data matching', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'set_configuration' => $this->fromConfiguration?->getId(),\n 'old_state' => [\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ],\n ]);\n\n $this->resetCrmMappings($activity, $activityRepository);\n\n $this->switchCrmConfigurationIfNeeded($activity);\n\n $activity->refresh();\n\n $crmActivityService->updateCrmData(\n activity: $activity,\n remoteSearch: $this->remoteSearch,\n );\n\n $hasMatch = $activity->getLead() !== null\n || $activity->getContact() !== null\n || $activity->getAccount() !== null\n || $activity->getOpportunity() !== null;\n\n if ($hasMatch) {\n Log::info('[MatchActivityCrmData] Successfully matched CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ]);\n } else {\n Log::info('[MatchActivityCrmData] No CRM match found', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n ]);\n }\n });\n } catch (Throwable $e) {\n Log::error('[MatchActivityCrmData] Failed to match CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'exception' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n\n throw $e;\n }\n }\n\n public function failed(Throwable $exception): void\n {\n Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'from_configuration' => $this->fromConfiguration?->getId(),\n 'exception' => $exception->getMessage(),\n 'attempts' => $this->attempts(),\n ]);\n }\n\n private function resetCrmMappings(\n Activity $activity,\n ActivityRepository $activityRepository\n ): void {\n $activity->update([\n 'lead_id' => null,\n 'contact_id' => null,\n 'account_id' => null,\n 'opportunity_id' => null,\n 'stage_id' => null,\n ]);\n\n $participantsOldState = $activityRepository->getActivityParticipants($activity)\n ->map(function ($participant) {\n return [\n 'id' => $participant->id,\n 'user_id' => $participant->user_id,\n 'contact_id' => $participant->contact_id,\n 'lead_id' => $participant->lead_id,\n ];\n });\n\n if ($participantsOldState->isNotEmpty()) {\n Log::info('[MatchActivityCrmData] Participants old state', [\n 'activity' => $this->activityId,\n 'participants' => $participantsOldState->toArray(),\n ]);\n }\n\n $activity->participants()->update([\n 'user_id' => null,\n 'contact_id' => null,\n 'lead_id' => null,\n ]);\n }\n\n private function switchCrmConfigurationIfNeeded(Activity $activity): void\n {\n if ($this->fromConfiguration === null) {\n return;\n }\n\n if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {\n return;\n }\n\n Log::info('[MatchActivityCrmData] Switching CRM configuration', [\n 'activity' => $this->activityId,\n 'old_configuration' => $activity->getCrm()?->getId(),\n 'new_configuration' => $this->fromConfiguration->getId(),\n ]);\n\n $activity->update([\n 'crm_configuration_id' => $this->fromConfiguration->getId(),\n 'crm_provider_id' => null,\n ]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8932599970913322963
|
6666527977935931696
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected...
|
6144
|
NULL
|
NULL
|
NULL
|
|
6239
|
257
|
7
|
2026-05-07T18:01:40.446529+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778176900446_m1.jpg...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master<br/>Some incoming commits are not fetched<br/>","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\Utility\\Service;\n\nuse Illuminate\\Cache\\RateLimiter;\nuse Jiminny\\Contracts\\Http\\RateLimited;\nuse Jiminny\\Contracts\\Http\\RateLimitInterface;\n\nclass ProviderRateLimiter\n{\n protected RateLimiter $rateLimiter;\n\n public function __construct(RateLimiter $rateLimiter)\n {\n $this->rateLimiter = $rateLimiter;\n }\n\n public function canMakeRequest(RateLimited $provider): bool\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $key = $rateLimit->getKey();\n\n if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {\n return false;\n }\n }\n\n return true;\n }\n\n public function requestAvailableIn(RateLimited $provider): int\n {\n return $provider->getRateLimits()->isNotEmpty()\n ? $provider->getRateLimits()\n ->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))\n ->max()\n : 0\n ;\n }\n\n public function incrementRequestCount(RateLimited $provider): void\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\Utility\\Service;\n\nuse Illuminate\\Cache\\RateLimiter;\nuse Jiminny\\Contracts\\Http\\RateLimited;\nuse Jiminny\\Contracts\\Http\\RateLimitInterface;\n\nclass ProviderRateLimiter\n{\n protected RateLimiter $rateLimiter;\n\n public function __construct(RateLimiter $rateLimiter)\n {\n $this->rateLimiter = $rateLimiter;\n }\n\n public function canMakeRequest(RateLimited $provider): bool\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $key = $rateLimit->getKey();\n\n if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {\n return false;\n }\n }\n\n return true;\n }\n\n public function requestAvailableIn(RateLimited $provider): int\n {\n return $provider->getRateLimits()->isNotEmpty()\n ? $provider->getRateLimits()\n ->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))\n ->max()\n : 0\n ;\n }\n\n public function incrementRequestCount(RateLimited $provider): void\n {\n /** @var RateLimitInterface $rateLimit */\n foreach ($provider->getRateLimits() as $rateLimit) {\n $this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.016666668,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Connection;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmActivityService;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\n\nclass MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n public int $tries = 3;\n\n private int $activityId;\n private ?Configuration $fromConfiguration;\n private bool $remoteSearch;\n\n public function middleware(): array\n {\n return [new HandleHubspotRateLimit()];\n }\n\n public function __construct(\n int $activityId,\n ?Configuration $fromConfiguration = null,\n bool $remoteSearch = false,\n ) {\n $this->activityId = $activityId;\n $this->fromConfiguration = $fromConfiguration;\n $this->remoteSearch = $remoteSearch;\n\n $this->onQueue(Constants::QUEUE_ANALYTICS_LOW);\n }\n\n public function uniqueId(): string\n {\n $configId = $this->fromConfiguration?->getId() ?? 0;\n $remote = $this->remoteSearch ? 'remote' : 'local';\n\n return \"$this->activityId:$configId:$remote\";\n }\n\n public function timeout(): int\n {\n return 300; // 5 minutes max execution time\n }\n\n public function uniqueFor(): int\n {\n return $this->timeout() + 60; // timeout + 1 minute buffer\n }\n\n public function backoff(): array\n {\n return [30, 90, 180];\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws Exception|Throwable\n */\n public function handle(\n ActivityRepository $activityRepository,\n CrmActivityService $crmActivityService,\n Connection $connection,\n ): void {\n $activity = $activityRepository->findById($this->activityId);\n if ($activity === null) {\n throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');\n }\n\n try {\n $connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {\n Log::info('[MatchActivityCrmData] Starting CRM data matching', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'set_configuration' => $this->fromConfiguration?->getId(),\n 'old_state' => [\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ],\n ]);\n\n $this->resetCrmMappings($activity, $activityRepository);\n\n $this->switchCrmConfigurationIfNeeded($activity);\n\n $activity->refresh();\n\n $crmActivityService->updateCrmData(\n activity: $activity,\n remoteSearch: $this->remoteSearch,\n );\n\n $hasMatch = $activity->getLead() !== null\n || $activity->getContact() !== null\n || $activity->getAccount() !== null\n || $activity->getOpportunity() !== null;\n\n if ($hasMatch) {\n Log::info('[MatchActivityCrmData] Successfully matched CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ]);\n } else {\n Log::info('[MatchActivityCrmData] No CRM match found', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n ]);\n }\n });\n } catch (Throwable $e) {\n Log::error('[MatchActivityCrmData] Failed to match CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'exception' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n\n throw $e;\n }\n }\n\n public function failed(Throwable $exception): void\n {\n Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'from_configuration' => $this->fromConfiguration?->getId(),\n 'exception' => $exception->getMessage(),\n 'attempts' => $this->attempts(),\n ]);\n }\n\n private function resetCrmMappings(\n Activity $activity,\n ActivityRepository $activityRepository\n ): void {\n $activity->update([\n 'lead_id' => null,\n 'contact_id' => null,\n 'account_id' => null,\n 'opportunity_id' => null,\n 'stage_id' => null,\n ]);\n\n $participantsOldState = $activityRepository->getActivityParticipants($activity)\n ->map(function ($participant) {\n return [\n 'id' => $participant->id,\n 'user_id' => $participant->user_id,\n 'contact_id' => $participant->contact_id,\n 'lead_id' => $participant->lead_id,\n ];\n });\n\n if ($participantsOldState->isNotEmpty()) {\n Log::info('[MatchActivityCrmData] Participants old state', [\n 'activity' => $this->activityId,\n 'participants' => $participantsOldState->toArray(),\n ]);\n }\n\n $activity->participants()->update([\n 'user_id' => null,\n 'contact_id' => null,\n 'lead_id' => null,\n ]);\n }\n\n private function switchCrmConfigurationIfNeeded(Activity $activity): void\n {\n if ($this->fromConfiguration === null) {\n return;\n }\n\n if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {\n return;\n }\n\n Log::info('[MatchActivityCrmData] Switching CRM configuration', [\n 'activity' => $this->activityId,\n 'old_configuration' => $activity->getCrm()?->getId(),\n 'new_configuration' => $this->fromConfiguration->getId(),\n ]);\n\n $activity->update([\n 'crm_configuration_id' => $this->fromConfiguration->getId(),\n 'crm_provider_id' => null,\n ]);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm;\n\nuse Exception;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Connection;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Jobs\\Job;\nuse Jiminny\\Jobs\\Middleware\\HandleHubspotRateLimit;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmActivityService;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\n\nclass MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique\n{\n use InteractsWithQueue;\n use SerializesModels;\n\n public int $tries = 3;\n\n private int $activityId;\n private ?Configuration $fromConfiguration;\n private bool $remoteSearch;\n\n public function middleware(): array\n {\n return [new HandleHubspotRateLimit()];\n }\n\n public function __construct(\n int $activityId,\n ?Configuration $fromConfiguration = null,\n bool $remoteSearch = false,\n ) {\n $this->activityId = $activityId;\n $this->fromConfiguration = $fromConfiguration;\n $this->remoteSearch = $remoteSearch;\n\n $this->onQueue(Constants::QUEUE_ANALYTICS_LOW);\n }\n\n public function uniqueId(): string\n {\n $configId = $this->fromConfiguration?->getId() ?? 0;\n $remote = $this->remoteSearch ? 'remote' : 'local';\n\n return \"$this->activityId:$configId:$remote\";\n }\n\n public function timeout(): int\n {\n return 300; // 5 minutes max execution time\n }\n\n public function uniqueFor(): int\n {\n return $this->timeout() + 60; // timeout + 1 minute buffer\n }\n\n public function backoff(): array\n {\n return [30, 90, 180];\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws Exception|Throwable\n */\n public function handle(\n ActivityRepository $activityRepository,\n CrmActivityService $crmActivityService,\n Connection $connection,\n ): void {\n $activity = $activityRepository->findById($this->activityId);\n if ($activity === null) {\n throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');\n }\n\n try {\n $connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {\n Log::info('[MatchActivityCrmData] Starting CRM data matching', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'set_configuration' => $this->fromConfiguration?->getId(),\n 'old_state' => [\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ],\n ]);\n\n $this->resetCrmMappings($activity, $activityRepository);\n\n $this->switchCrmConfigurationIfNeeded($activity);\n\n $activity->refresh();\n\n $crmActivityService->updateCrmData(\n activity: $activity,\n remoteSearch: $this->remoteSearch,\n );\n\n $hasMatch = $activity->getLead() !== null\n || $activity->getContact() !== null\n || $activity->getAccount() !== null\n || $activity->getOpportunity() !== null;\n\n if ($hasMatch) {\n Log::info('[MatchActivityCrmData] Successfully matched CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'lead_id' => $activity->getLead()?->getId(),\n 'contact_id' => $activity->getContact()?->getId(),\n 'account_id' => $activity->getAccount()?->getId(),\n 'opportunity_id' => $activity->getOpportunity()?->getId(),\n 'stage_id' => $activity->getStage()?->getId(),\n ]);\n } else {\n Log::info('[MatchActivityCrmData] No CRM match found', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n ]);\n }\n });\n } catch (Throwable $e) {\n Log::error('[MatchActivityCrmData] Failed to match CRM data', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'exception' => $e->getMessage(),\n 'trace' => $e->getTraceAsString(),\n ]);\n\n throw $e;\n }\n }\n\n public function failed(Throwable $exception): void\n {\n Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [\n 'activity' => $this->activityId,\n 'remote_search' => $this->remoteSearch,\n 'from_configuration' => $this->fromConfiguration?->getId(),\n 'exception' => $exception->getMessage(),\n 'attempts' => $this->attempts(),\n ]);\n }\n\n private function resetCrmMappings(\n Activity $activity,\n ActivityRepository $activityRepository\n ): void {\n $activity->update([\n 'lead_id' => null,\n 'contact_id' => null,\n 'account_id' => null,\n 'opportunity_id' => null,\n 'stage_id' => null,\n ]);\n\n $participantsOldState = $activityRepository->getActivityParticipants($activity)\n ->map(function ($participant) {\n return [\n 'id' => $participant->id,\n 'user_id' => $participant->user_id,\n 'contact_id' => $participant->contact_id,\n 'lead_id' => $participant->lead_id,\n ];\n });\n\n if ($participantsOldState->isNotEmpty()) {\n Log::info('[MatchActivityCrmData] Participants old state', [\n 'activity' => $this->activityId,\n 'participants' => $participantsOldState->toArray(),\n ]);\n }\n\n $activity->participants()->update([\n 'user_id' => null,\n 'contact_id' => null,\n 'lead_id' => null,\n ]);\n }\n\n private function switchCrmConfigurationIfNeeded(Activity $activity): void\n {\n if ($this->fromConfiguration === null) {\n return;\n }\n\n if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {\n return;\n }\n\n Log::info('[MatchActivityCrmData] Switching CRM configuration', [\n 'activity' => $this->activityId,\n 'old_configuration' => $activity->getCrm()?->getId(),\n 'new_configuration' => $this->fromConfiguration->getId(),\n ]);\n\n $activity->update([\n 'crm_configuration_id' => $this->fromConfiguration->getId(),\n 'crm_provider_id' => null,\n ]);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8932599970913322963
|
6666527977935931696
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected...
|
6237
|
NULL
|
NULL
|
NULL
|
|
20838
|
905
|
12
|
2026-05-11T16:21:56.037050+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-11/1778 /Users/lukas/.screenpipe/data/data/2026-05-11/1778516516037_m2.jpg...
|
Firefox
|
JY-20725 add HS rate limit handling on activities JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app — Work...
|
True
|
github.com/jiminny/app/pull/12066
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 3 hours ago
LakyLak
LakyLak
commented
3 hours ago
3 hours ago
JIRA: JY-20725
JIRA:
JY-20725
JY-20725
Changes:
Changes:
Adds
HandleHubspotRateLimit
middleware (release jobs with delay)
Adds
executeRequest
wrapper in Hubspot Client. It handles 429 errors and throws
RateLimitException
Add or remove reactions
LakyLak
LakyLak
added
2
commits
3 hours ago
3 hours ago
@LakyLak
JY-20725
JY-20725
add HS rate limit handling on activities rematching
add HS rate limit handling on activities rematching
8 / 10 checks OK
44d5678
44d5678
@LakyLak
JY-20725
JY-20725
add tests
add tests
11 / 12 checks OK
cbed2c9
cbed2c9
@sonarqubecloud
Show options
sonarqubecloud Bot commented 2 hours ago
sonarqubecloud
sonarqubecloud
Bot
commented
2 hours ago
2 hours ago
Quality Gate Failed Quality Gate failed
Quality Gate Failed
Quality Gate failed
Failed conditions
34.4% Coverage on New Code
34.4% Coverage on New Code
(required ≥ 70%)
See analysis details on SonarQube Cloud
See analysis details on SonarQube Cloud
Add or remove reactions
@LakyLak
Show options
Author
LakyLak commented 2 hours ago
LakyLak
LakyLak
commented
2 hours ago
2 hours ago
@claude
@claude...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.16888298,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.16140293,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.18816489,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.18994413,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.28252193,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (34)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"34","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (4)","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Important update","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Important update","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Review this update","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Review this update","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and manage your preferences in your","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"GitHub account settings","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"GitHub account settings","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dismiss banner","depth":9,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"JY-20725 add HS rate limit handling on activities rematching #12066 Edit title","depth":13,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12066","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Awaiting approval","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Awaiting approval","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 4 commits into","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20725-handle-HS-search-rate-limit","depth":16,"on_screen":false,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725-handle-HS-search-rate-limit","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 757 additions & 249 deletions","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (3)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (4)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (2)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (12)","depth":16,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":14,"bounds":{"left":0.1008976,"top":0.0726257,"width":0.011968086,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20725 add HS rate limit handling on activities rematching #12066 LakyLak wants to merge 4 commits into master from JY-20725-handle-HS-search-rate-limit Copy head branch name to clipboard","depth":14,"bounds":{"left":0.11951463,"top":0.058260176,"width":0.21077128,"height":0.042298485},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20725 add HS rate limit handling on activities rematching","depth":16,"bounds":{"left":0.11951463,"top":0.05865922,"width":0.13663563,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching","depth":17,"bounds":{"left":0.11951463,"top":0.06304868,"width":0.13663563,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":16,"bounds":{"left":0.25880983,"top":0.06304868,"width":0.0028257978,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12066","depth":16,"bounds":{"left":0.26163563,"top":0.06304868,"width":0.013630319,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":18,"bounds":{"left":0.11951463,"top":0.08339984,"width":0.016289894,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":19,"bounds":{"left":0.11951463,"top":0.08339984,"width":0.016289894,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 4 commits into","depth":18,"bounds":{"left":0.13713431,"top":0.08339984,"width":0.058011968,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":18,"bounds":{"left":0.19647606,"top":0.08180367,"width":0.018450798,"height":0.015163607},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":19,"bounds":{"left":0.19847074,"top":0.083798885,"width":0.014461436,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":19,"bounds":{"left":0.21625665,"top":0.08339984,"width":0.008643617,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20725-handle-HS-search-rate-limit","depth":19,"bounds":{"left":0.22623006,"top":0.08180367,"width":0.09042553,"height":0.015163607},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725-handle-HS-search-rate-limit","depth":20,"bounds":{"left":0.22822474,"top":0.083798885,"width":0.08643617,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":19,"bounds":{"left":0.3179854,"top":0.07821229,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Conversation","depth":12,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 3 hours ago","depth":14,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"3 hours ago","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3 hours ago","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20725","depth":16,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20725","depth":17,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.114527926,"top":0.0,"width":0.2521609,"height":0.01396648},"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.114527926,"top":0.0,"width":0.021276595,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adds","depth":18,"bounds":{"left":0.12250665,"top":0.0,"width":0.012300532,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HandleHubspotRateLimit","depth":19,"bounds":{"left":0.13480718,"top":0.0,"width":0.056349736,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"middleware (release jobs with delay)","depth":18,"bounds":{"left":0.19115691,"top":0.0,"width":0.07978723,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Adds","depth":18,"bounds":{"left":0.12250665,"top":0.0,"width":0.012300532,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"executeRequest","depth":19,"bounds":{"left":0.13480718,"top":0.0,"width":0.036070477,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wrapper in Hubspot Client. It handles 429 errors and throws","depth":18,"bounds":{"left":0.17087767,"top":0.0,"width":0.1314827,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"RateLimitException","depth":19,"bounds":{"left":0.3023604,"top":0.0,"width":0.043550532,"height":0.013567438},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.114527926,"top":0.0,"width":0.008643617,"height":0.0207502},"on_screen":false,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"LakyLak","depth":14,"bounds":{"left":0.12250665,"top":0.061851557,"width":0.01861702,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":15,"bounds":{"left":0.12250665,"top":0.061851557,"width":0.01861702,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"added","depth":14,"bounds":{"left":0.14112367,"top":0.061851557,"width":0.016123671,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":14,"bounds":{"left":0.15724733,"top":0.061851557,"width":0.0028257978,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commits","depth":14,"bounds":{"left":0.16007313,"top":0.061851557,"width":0.020944148,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"3 hours ago","depth":14,"bounds":{"left":0.1810173,"top":0.061851557,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"3 hours ago","depth":16,"bounds":{"left":0.1810173,"top":0.061851557,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.12250665,"top":0.10175578,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20725","depth":14,"bounds":{"left":0.13115026,"top":0.105347164,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725","depth":15,"bounds":{"left":0.13115026,"top":0.105347164,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"add HS rate limit handling on activities rematching","depth":14,"bounds":{"left":0.15275931,"top":0.105347164,"width":0.122340426,"height":0.011572227},"on_screen":true,"help_text":"JY-20725 add HS rate limit handling on activities rematching","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"add HS rate limit handling on activities rematching","depth":15,"bounds":{"left":0.15275931,"top":0.105347164,"width":0.122340426,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"8 / 10 checks OK","depth":14,"bounds":{"left":0.3489029,"top":0.10175578,"width":0.005319149,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"44d5678","depth":14,"bounds":{"left":0.35555187,"top":0.105347164,"width":0.016788565,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"44d5678","depth":15,"bounds":{"left":0.35555187,"top":0.105347164,"width":0.016788565,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.12250665,"top":0.14325619,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20725","depth":14,"bounds":{"left":0.13115026,"top":0.14684756,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725","depth":15,"bounds":{"left":0.13115026,"top":0.14684756,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"add tests","depth":14,"bounds":{"left":0.15275931,"top":0.14684756,"width":0.021609042,"height":0.011572227},"on_screen":true,"help_text":"JY-20725 add tests","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"add tests","depth":15,"bounds":{"left":0.15275931,"top":0.14684756,"width":0.021609042,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"11 / 12 checks OK","depth":14,"bounds":{"left":0.3489029,"top":0.14325619,"width":0.005319149,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"cbed2c9","depth":14,"bounds":{"left":0.35555187,"top":0.14684756,"width":0.016788565,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"cbed2c9","depth":15,"bounds":{"left":0.35555187,"top":0.14684756,"width":0.016788565,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@sonarqubecloud","depth":13,"bounds":{"left":0.090259306,"top":0.19034317,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.3587101,"top":0.19114126,"width":0.007978723,"height":0.02952913},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"sonarqubecloud Bot commented 2 hours ago","depth":13,"bounds":{"left":0.114527926,"top":0.19114126,"width":0.23620346,"height":0.029928172},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"sonarqubecloud","depth":15,"bounds":{"left":0.114527926,"top":0.1991221,"width":0.036402926,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"sonarqubecloud","depth":16,"bounds":{"left":0.114527926,"top":0.1991221,"width":0.036402926,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Bot","depth":16,"bounds":{"left":0.15442154,"top":0.20071827,"width":0.0066489363,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.16472739,"top":0.1991221,"width":0.02543218,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 hours ago","depth":14,"bounds":{"left":0.19148937,"top":0.19752593,"width":0.025598405,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 hours ago","depth":16,"bounds":{"left":0.19148937,"top":0.1991221,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Quality Gate Failed Quality Gate failed","depth":16,"bounds":{"left":0.114527926,"top":0.23463687,"width":0.2521609,"height":0.026735835},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"Quality Gate Failed","depth":17,"bounds":{"left":0.114527926,"top":0.23503591,"width":0.0066489363,"height":0.019952115},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Quality Gate failed","depth":18,"bounds":{"left":0.12283909,"top":0.23503591,"width":0.05867686,"height":0.019952115},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Failed conditions","depth":17,"bounds":{"left":0.114527926,"top":0.27573824,"width":0.036236703,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"34.4% Coverage on New Code","depth":17,"bounds":{"left":0.12117686,"top":0.292498,"width":0.06615692,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"34.4% Coverage on New Code","depth":18,"bounds":{"left":0.12117686,"top":0.292498,"width":0.06615692,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(required ≥ 70%)","depth":17,"bounds":{"left":0.18733378,"top":0.292498,"width":0.037732713,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"See analysis details on SonarQube Cloud","depth":17,"bounds":{"left":0.114527926,"top":0.32202715,"width":0.08809841,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"See analysis details on SonarQube Cloud","depth":18,"bounds":{"left":0.114527926,"top":0.32202715,"width":0.08809841,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":15,"bounds":{"left":0.114527926,"top":0.3499601,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@LakyLak","depth":13,"bounds":{"left":0.090259306,"top":0.40981644,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":14,"bounds":{"left":0.3587101,"top":0.41061452,"width":0.007978723,"height":0.02952913},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Author","depth":15,"bounds":{"left":0.34075797,"top":0.4197925,"width":0.012965426,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"LakyLak commented 2 hours ago","depth":13,"bounds":{"left":0.114527926,"top":0.41061452,"width":0.21725398,"height":0.02952913},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"bounds":{"left":0.114527926,"top":0.41859537,"width":0.01861702,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"bounds":{"left":0.114527926,"top":0.41859537,"width":0.01861702,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":14,"bounds":{"left":0.13447474,"top":0.41859537,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"2 hours ago","depth":14,"bounds":{"left":0.16140293,"top":0.4169992,"width":0.02543218,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"2 hours ago","depth":16,"bounds":{"left":0.16140293,"top":0.41859537,"width":0.02543218,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@claude","depth":17,"bounds":{"left":0.114527926,"top":0.45530728,"width":0.019281914,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"@claude","depth":18,"bounds":{"left":0.114527926,"top":0.45530728,"width":0.019281914,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8932384945198190491
|
7063762910030902656
|
visual_change
|
accessibility
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 3 hours ago
LakyLak
LakyLak
commented
3 hours ago
3 hours ago
JIRA: JY-20725
JIRA:
JY-20725
JY-20725
Changes:
Changes:
Adds
HandleHubspotRateLimit
middleware (release jobs with delay)
Adds
executeRequest
wrapper in Hubspot Client. It handles 429 errors and throws
RateLimitException
Add or remove reactions
LakyLak
LakyLak
added
2
commits
3 hours ago
3 hours ago
@LakyLak
JY-20725
JY-20725
add HS rate limit handling on activities rematching
add HS rate limit handling on activities rematching
8 / 10 checks OK
44d5678
44d5678
@LakyLak
JY-20725
JY-20725
add tests
add tests
11 / 12 checks OK
cbed2c9
cbed2c9
@sonarqubecloud
Show options
sonarqubecloud Bot commented 2 hours ago
sonarqubecloud
sonarqubecloud
Bot
commented
2 hours ago
2 hours ago
Quality Gate Failed Quality Gate failed
Quality Gate Failed
Quality Gate failed
Failed conditions
34.4% Coverage on New Code
34.4% Coverage on New Code
(required ≥ 70%)
See analysis details on SonarQube Cloud
See analysis details on SonarQube Cloud
Add or remove reactions
@LakyLak
Show options
Author
LakyLak commented 2 hours ago
LakyLak
LakyLak
commented
2 hours ago
2 hours ago
@claude
@claude...
|
20837
|
NULL
|
NULL
|
NULL
|
|
1521
|
60
|
11
|
2026-05-07T08:52:06.606628+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778143926606_m2.jpg...
|
PhpStorm
|
faVsco.js – DeleteCrmEntityTrait.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Code changed:
Hide
Sync Changes
Hide This Notification
37
1
35
64
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN ("0069N000003GIQ9QAO","0061r000019yGP9AAM","0066900001S2KWlAAN","0066900001TDpj2AAD","0066900001b8uEwAAI","0069N000001rQi0QAE","006QF00000KD40mYAD","006QF00000LzpRJYAZ","0069N000002uomtQAA","0069N000002xlMLQAY","0066900001NV6ubAAD","0061r00001HJp45AAD","006QF00000uTlUoYAK","006QF00000v0bZqYAI");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * from participants where activity_id = 58081273;
select * from activities where crm_configuration_id = 537 and provider = 'aircall'
and account_id = 19003658 order by updated_at desc;
select * from contacts where crm_configuration_id = 537 and id = 35957759;
select * from accounts where crm_configuration_id = 537 and id = 19003658;
select * from automated_report_results where id = 1976;
select * from automated_reports where id = 583;
select * from activity_searches where id = 87714;
select * from activity_search_filters where activity_search_id = 87714;
SELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue
Job
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php, class
Constants.php, class
ProcessingQueueConstants.php, class
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary
Twilio
Uploader
UrlGenerator
Utility
Exceptions
Service
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid
Waveform
Webhooks
Workflow
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights
Dev
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.38297874,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.39162233,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.40259308,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4112367,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.41988033,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.43085107,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.024268618,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.46841756,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.4793883,"top":0.09896249,"width":0.029587766,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.65359044,"top":0.09896249,"width":0.02825798,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"37","depth":4,"bounds":{"left":0.62333775,"top":0.123703115,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.6353058,"top":0.123703115,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"35","depth":4,"bounds":{"left":0.64461434,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"64","depth":4,"bounds":{"left":0.6569149,"top":0.123703115,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.66888297,"top":0.12210695,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6761968,"top":0.12210695,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;","depth":4,"on_screen":true,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports;\nwhere id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;\n\nselect * from leads;\n\nselect * from calendars;\n\nSELECT\n t.id AS team_id,\n t.name,\n LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain\nFROM teams t\nJOIN users u ON u.team_id = t.id\nJOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'\nLEFT JOIN team_domains td\n ON td.team_id = t.id\n AND td.deleted_at IS NULL\n AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))\nGROUP BY t.id, t.name, calendar_domain\nORDER BY t.name, calendar_domain;\n\nselect * from users u join calendars c on c.user_id = u.id\nwhere u.team_id = 882;\n\n\nselect * from activities where id = 74049485; # team 563 crm 537\nselect * from activities where id = 73272382; # team 563 crm 537\nselect * from activities where id = 64400389; # team 563 crm 537\nselect * from activities where id = 58081273; # team 563 crm 537\nselect * from activities where id = 54520297; # team 563 crm 537\nselect * from participants where activity_id = 58081273;\n\nselect * from activities where crm_configuration_id = 537 and provider = 'aircall'\nand account_id = 19003658 order by updated_at desc;\n\nselect * from contacts where crm_configuration_id = 537 and id = 35957759;\nselect * from accounts where crm_configuration_id = 537 and id = 19003658;\n\nselect * from automated_report_results where id = 1976;\nselect * from automated_reports where id = 583;\nselect * from activity_searches where id = 87714;\nselect * from activity_search_filters where activity_search_id = 87714;\n\nSELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid\nor uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot';\nselect * from rate_limits;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.35738033,"top":0.17478053,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.36702126,"top":0.17318435,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.3743351,"top":0.17318435,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","depth":4,"bounds":{"left":0.11968085,"top":0.17158818,"width":0.3081782,"height":0.8284118},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Crm\\Delete;\n\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Crm\\DetachActivityObject;\nuse Jiminny\\Models\\Activity;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\ntrait DeleteCrmEntityTrait\n{\n public int $tries = 3;\n\n public function timeout(): int\n {\n return 300; // 5 minutes\n }\n\n public function backoff(): array\n {\n return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes\n }\n\n protected function handleActivities(\n Collection $activities,\n Dispatcher $dispatcher,\n LoggerInterface $logger,\n bool $emitEvent = true,\n ): void {\n if ($activities->isEmpty()) {\n return;\n }\n\n $crmObject = $this->getEntityType();\n $entityIdField = $crmObject->value . '_id';\n\n $activities->each(\n function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {\n $stageId = $activity->getStage()?->getId();\n\n $logData = [\n $crmObject->value => $this->id,\n 'activity' => $activity->getId(),\n 'emitEvent' => $emitEvent,\n ];\n\n $updateData = [\n $entityIdField => null,\n ];\n\n // For leads and opportunities, also nullify the stage_id\n if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {\n $updateData['stage_id'] = null;\n $logData['stage_id'] = $stageId;\n }\n\n $activity->update($updateData);\n\n if ($emitEvent) {\n $dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));\n }\n\n $logger->info($this->getLogPrefix() . ' Detach from activity', $logData);\n\n // Dispatch job to verify if CRM task/event still exists\n if ($activity->hasCrmProviderId()) {\n VerifyActivityCrmTaskJob::dispatch($activity->getId());\n }\n }\n );\n }\n\n public function failed(Throwable $exception): void\n {\n $crmObject = $this->getEntityType();\n\n Log::critical($this->getLogPrefix() . ' Job failed permanently', [\n $crmObject->value => $this->id,\n 'exception' => $exception->getMessage(),\n ]);\n }\n\n // Abstract methods that must be implemented by the using class\n abstract protected function getLogPrefix(): string;\n abstract protected function getEntityType(): CrmObject;\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Job","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uploader","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Utility","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uuid","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Waveform","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Workflow","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddRateLimitCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixHubSpotTokens.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixMissMatchedCrmActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MonitorSocialAccountsState.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php, abstract class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php, class","depth":10,"on_screen":false,"role_description":"text"}]...
|
8931963538553068821
|
1065696262816675397
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Code changed:
Hide
Sync Changes
Hide This Notification
37
1
35
64
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1068 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262
SELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
select * from crm_layouts where crm_configuration_id = 834;
select * from crm_layout_entities where crm_layout_id = 2780;
select * from crm_fields where id IN (321153,321192,321193,321194);
SELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1057 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8
SELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20
SELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last
SELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10
SELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;
SELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #
SELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;
select * from users where team_id = 51; # 7783
SELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130
select * from activity_searches where user_id = 7783;
select * from activity_search_filters where activity_search_id IN (32291, 32292);
SELECT asf.activity_search_id, asf.id, asf.value
FROM activity_search_filters asf
WHERE asf.filter = 'group_id'
AND asf.value IN (
SELECT CONCAT(
HEX(SUBSTR(uuid, 5, 4)), '-',
HEX(SUBSTR(uuid, 3, 2)), '-',
HEX(SUBSTR(uuid, 1, 2)), '-',
HEX(SUBSTR(uuid, 9, 2)), '-',
HEX(SUBSTR(uuid, 11))
)
FROM groups
WHERE deleted_at IS NOT NULL
);
SELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where provider = 'hubspot';
SELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133
SELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;
SELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null
# [PASSWORD_DOTS]
select * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';
select
cp.*
# DISTINCT t.id
# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields
FROM crm_profiles cp
JOIN crm_configurations crm on crm.id = cp.crm_configuration_id
JOIN users u on u.id = cp.user_id
JOIN teams t ON t.id = crm.team_id
WHERE crm.provider = 'salesforce' and t.status = 'active'
and cp.archived_at IS NULL and u.deleted_at IS NULL
and t.id NOT IN (1093)
and t.id = 2
and cp.contact_fields IS NULL;
# and c.crm_provider_id = '003Uu00000ojD4NIAU';
SELECT * FROM users WHERE id = 26484;
SELECT * FROM crm_profiles WHERE user_id = 26484;
SELECT * FROM social_accounts WHERE sociable_id = 26484;
SELECT * FROM crm_configurations where provider = 'salesforce';
select * from users where id IN (10022, 10403);
select * from users where team_id IN (526);
select * from teams where id IN (526, 532);
select * from crm_configurations where id IN (500, 516);
select * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);
select * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 526 and sa.provider = 'salesforce';
select * from team_settings where team_id IN (526, 532);
select * from users where id IN (22824);
select * from crm_profiles where crm_configuration_id IN (1026);
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1093 and sa.provider = 'salesforce';
select * from teams where id = 1099;
select * from users where id = 29643
select * from activity_processing_states;
SELECT * FROM teams where name LIKE '%Fare%'; # 233
SELECT * FROM opportunities where crm_configuration_id = 215
# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'
;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1088 and sa.provider = 'hubspot';
SELECT * FROM teams order by updated_at DESC
SELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account
select * from crm_configurations where provider = 'pipedrive';
select * from teams where id = 957;
select * from crm_configurations where id = 957;
SELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743
SELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;
select * from users where team_id = 1; # 26726 - Gabriela Dureva
SELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific
select * from activities where user_id = 26726 order by id desc;
select * from contacts where crm_configuration_id = 1
and email IN ('[EMAIL]', '[EMAIL]'); # 2094416, 2093620
SELECT * FROM contacts WHERE id = 6284931;
SELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id
WHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;
select * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);
select * from crm_configurations where id = 1;
43801692-1aeb-32ce-acba-5b80a479701a
44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b
405975c0-b3d0-7aaa-821f-09d59cae6dd1
4caf848d-4bed-2299-b248-7788d41f9fca
49bedc3f-f196-eef3-89c3-dea6a3b4aa63
43420989-a09d-b8f8-9806-c8bbf7a02aac
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
SELECT * FROM activities WHERE id = 75461988;
SELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;
select * from contacts where id = 17900517;
select * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id
where crm.provider != 'salesforce';
select * from users where id = 21047;
SELECT * FROM crm_configurations WHERE id = 892;
SELECT * FROM teams WHERE id = 942;
select * from opportunities where team_id = 942 order by updated_at desc;
select * from contacts where team_id = 942 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 942 and sa.provider = 'hubspot';
SELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430
SELECT * FROM crm_configurations WHERE id = 1;
SELECT * FROM teams WHERE crm_id = 1;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1 and sa.provider = 'salesforce';
select id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1
SELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430
select * from teams where id = 852;
select * from groups where id = 2286;
select * from sidekick_settings where team_id = 852;
select * from default_activity_types where team_id = 852;
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1 AND u.deleted_at IS NULL
AND u.crm_required = 1
AND u.team_id = 1
ORDER BY u.team_id;
SELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (
18481
);
SELECT cc.provider, cc.id, p.id, u.*
FROM users u
LEFT JOIN crm_profiles p ON u.id = p.user_id
INNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'
INNER JOIN crm_configurations cc ON t.crm_id = cc.id
WHERE u.status = 1
AND u.deleted_at IS NULL
AND u.crm_required = 1
# AND u.team_id = 1
AND p.id IS NULL -- Move this condition to WHERE clause
ORDER BY u.team_id;
SELECT * FROM opportunities WHERE id = 20002609;
select * from teams where id = 1122; # Velatir, 29953 - [EMAIL]
select * from crm_configurations where id = 1060;
select * from crm_layouts where crm_configuration_id = 1060;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1122 and sa.provider = 'hubspot';
select * from opportunities where team_id = 1122 order by updated_at desc;
select * from crm_field_data where object_type = 'contact';
SELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 248 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS
SELECT * FROM users where id = 24115;
SELECT * FROM accounts where id = 4002896;
SELECT * FROM teams WHERE name LIKE '%adswerve%';
SELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN ("0069N000003GIQ9QAO","0061r000019yGP9AAM","0066900001S2KWlAAN","0066900001TDpj2AAD","0066900001b8uEwAAI","0069N000001rQi0QAE","006QF00000KD40mYAD","006QF00000LzpRJYAZ","0069N000002uomtQAA","0069N000002xlMLQAY","0066900001NV6ubAAD","0061r00001HJp45AAD","006QF00000uTlUoYAK","006QF00000v0bZqYAI");
SELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203
SELECT u.id, u.email, ac.name, a.* FROM activities a
JOIN users u ON a.user_id = u.id
JOIN accounts ac ON a.account_id = ac.id
WHERE
uuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or
uuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or
uuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;
select * from users where id = 5825;
SELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;
select * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;
19594, 862
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 862 and sa.provider = 'salesforce';
select * from automated_reports where id = 36;
select ar.frequency, r.*, ar.* from automated_report_results r
join automated_reports ar on r.report_id = ar.id
where ar.frequency != 'one_off';
select s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;
select * from nudges n where n.activity_search_id
select * from teams where created_at > '2026-03-09';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;
select * from users where team_id = 1 and name like '%Lukas%'; # 7160
SELECT * FROM teams WHERE id = 575;
select * from opportunities where team_id = 575;
SELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,
select * from opportunities where team_id = 1126;
SELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,
select * from opportunities where team_id = 1125;
select * from contacts c
where c.team_id = 882;
SELECT * FROM activities WHERE id = 76822967;
SELECT * FROM crm_profiles WHERE user_id = 15440;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 555;
SELECT * FROM crm_configurations WHERE id = 555;
SELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 581 and sa.provider = 'salesforce';
SELECT * FROM automated_report_results order by id desc;
select * from features;
select * from team_features where feature_id = 40;
select * from teams where id = 556;
select * from automated_reports;
where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , ["pdf","podcast"]
SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;
select * from automated_report_results order by id desc;
SELECT * FROM automated_report_results WHERE id = 1919;
select * from automated_report_results WHERE report_id = 54;
select * from opportunities where id = 7594349;
SELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067 - [EMAIL]
select * from playbooks where team_id = 711; # event 226147
SELECT * FROM playbook_categories WHERE playbook_id = 5515;
SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event';
SELECT * FROM crm_fields WHERE id = 226147;
SELECT * FROM crm_field_values WHERE crm_field_id = 226147;
SELECT * FROM crm_configurations WHERE id = 692;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 711 and sa.provider = 'salesforce';
SELECT * FROM crm_profiles cp JOIN users u on u.id = cp.user_id WHERE u.team_id = 711;
select * from leads;
select * from calendars;
SELECT
t.id AS team_id,
t.name,
LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1)) AS calendar_domain
FROM teams t
JOIN users u ON u.team_id = t.id
JOIN calendars c ON c.user_id = u.id AND c.status = 'active' AND c.calendar_provider_id LIKE '%@%'
LEFT JOIN team_domains td
ON td.team_id = t.id
AND td.deleted_at IS NULL
AND td.domain = LOWER(SUBSTRING_INDEX(c.calendar_provider_id, '@', -1))
GROUP BY t.id, t.name, calendar_domain
ORDER BY t.name, calendar_domain;
select * from users u join calendars c on c.user_id = u.id
where u.team_id = 882;
select * from activities where id = 74049485; # team 563 crm 537
select * from activities where id = 73272382; # team 563 crm 537
select * from activities where id = 64400389; # team 563 crm 537
select * from activities where id = 58081273; # team 563 crm 537
select * from activities where id = 54520297; # team 563 crm 537
select * from participants where activity_id = 58081273;
select * from activities where crm_configuration_id = 537 and provider = 'aircall'
and account_id = 19003658 order by updated_at desc;
select * from contacts where crm_configuration_id = 537 and id = 35957759;
select * from accounts where crm_configuration_id = 537 and id = 19003658;
select * from automated_report_results where id = 1976;
select * from automated_reports where id = 583;
select * from activity_searches where id = 87714;
select * from activity_search_filters where activity_search_id = 87714;
SELECT * FROM activities WHERE uuid_to_bin('8827f672-202d-4162-9d04-73ff5f0566a9') = uuid
or uuid_to_bin('47842446-af51-4bcb-854f-cc6560290101') = uuid;
SELECT * FROM crm_configurations WHERE provider = 'hubspot';
select * from rate_limits;
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Delete;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Crm\DetachActivityObject;
use Jiminny\Models\Activity;
use Psr\Log\LoggerInterface;
use Throwable;
trait DeleteCrmEntityTrait
{
public int $tries = 3;
public function timeout(): int
{
return 300; // 5 minutes
}
public function backoff(): array
{
return [30, 90, 180]; // 30 seconds, 1.5 minutes, 3 minutes
}
protected function handleActivities(
Collection $activities,
Dispatcher $dispatcher,
LoggerInterface $logger,
bool $emitEvent = true,
): void {
if ($activities->isEmpty()) {
return;
}
$crmObject = $this->getEntityType();
$entityIdField = $crmObject->value . '_id';
$activities->each(
function (Activity $activity) use ($dispatcher, $logger, $entityIdField, $crmObject, $emitEvent): void {
$stageId = $activity->getStage()?->getId();
$logData = [
$crmObject->value => $this->id,
'activity' => $activity->getId(),
'emitEvent' => $emitEvent,
];
$updateData = [
$entityIdField => null,
];
// For leads and opportunities, also nullify the stage_id
if ($stageId && in_array($crmObject, [CrmObject::LEAD, CrmObject::OPPORTUNITY], true)) {
$updateData['stage_id'] = null;
$logData['stage_id'] = $stageId;
}
$activity->update($updateData);
if ($emitEvent) {
$dispatcher->dispatch(new DetachActivityObject($activity, $crmObject));
}
$logger->info($this->getLogPrefix() . ' Detach from activity', $logData);
// Dispatch job to verify if CRM task/event still exists
if ($activity->hasCrmProviderId()) {
VerifyActivityCrmTaskJob::dispatch($activity->getId());
}
}
);
}
public function failed(Throwable $exception): void
{
$crmObject = $this->getEntityType();
Log::critical($this->getLogPrefix() . ' Job failed permanently', [
$crmObject->value => $this->id,
'exception' => $exception->getMessage(),
]);
}
// Abstract methods that must be implemented by the using class
abstract protected function getLogPrefix(): string;
abstract protected function getEntityType(): CrmObject;
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue
Job
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php, class
Constants.php, class
ProcessingQueueConstants.php, class
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary
Twilio
Uploader
UrlGenerator
Utility
Exceptions
Service
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid
Waveform
Webhooks
Workflow
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights
Dev
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
7409
|
330
|
29
|
2026-05-08T08:47:58.763298+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778230078763_m2.jpg...
|
PhpStorm
|
faVsco.js – Constants.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
IDE error occurred
text/html
text/html
text/html
S IDE error occurred
text/html
text/html
text/html
See details and submit report
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"IDE error occurred","depth":3,"bounds":{"left":0.8753325,"top":0.92577815,"width":0.11037234,"height":0.013567438},"on_screen":true,"value":"IDE error occurred","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.92577815,"width":0.03756649,"height":0.013567438},"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"See details and submit report","depth":2,"bounds":{"left":0.8753325,"top":0.943336,"width":0.06017287,"height":0.013567438},"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.040226065,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master<br/>Some incoming commits are not fetched<br/>","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.4225399,"top":0.09896249,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8931084062743970798
|
-8348545564741097022
|
visual_change
|
hybrid
|
NULL
|
IDE error occurred
text/html
text/html
text/html
S IDE error occurred
text/html
text/html
text/html
See details and submit report
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Execute
PhostormINavigarecodeFV faVsco.js?9 master k© MatchActivityCrmData.php• D Datadog© CrmActivityService.phpO Automatedkeponkesultonpo clientonpce) Constants.php_ Date limeRateLimitexception.php_ Dealinsignts_DealKISKSD ElasticSearchencodingEncrvptionD ES_ FeatureFlagsU FFMpeg17 FileSvstemN GeckoGongD GuzzleHttpM KevPointeiM Kiock• LanguageDetectionMliveFoedMlocks1M Math1M Media PinelineiN MootinaRotD MobileSettingsM ModalC Notification_ Nudae_ ParagraphBreaker_ ParticipantspeechPartitionedCookieaPlavista Prooheta Prophet All/a ProsperWorksQueuev llob© CheckAndRetryRemoteMatch.php© HandleHubspotRateLimit.phe( OpportunitySyncTrait.php©ImportBatchJobTrait.php© SyncHubspotObiects.php© HydrateCrmDataByExternalCallIdJob.php(C) ConterenceCrmMatcherJob.php© MatchCrmData.php@ Activitv.php(C) DefaultUpdateCrmDataResolver.oho© CachedCrmServiceDecorator.phpfinal cllass ConstantsCPateLimitAware.ohtC) Patelimit AwareWral@ RotsQueueConstente, 46© Constants.php(C) ProceccinaQueueConsM RouteSam|IN CAMi CoodorM ContrlM Corinlizorpublic const string CUSTOMER_API = 'jiminny.customer_api';usdgepublic const string ACTIVITY_LANGUAGE_DETECTION_SUCCESS = 'jiminny.activity.language_detection.success'iLusagespublic const string ACTIVITY_LANGUAGE_DETECTION_FAIL = 'jiminny.activity.language_detection.fail':Lusagespublic const string ASK_JIMINNY_DEAL_TEAM_REQUEST = 'jiminny.ask_jiminny_deals.team_write_requests'1usagepublic const string ASK_JIMINNY_DEAL_TEAM_READ_REQUEST = 'jiminny.ask_jiminny_deals.team_read_requests';Z usagespublic const string CRM_AUTO_SYNC = 'jiminny.crm.auto-sync';public const string ACTIVITIES DIALERS STATS = 'jiminny.activities.dialers.stats':public const string ACTIVITIES DIALERS REASON STATS = 'jiminny.activities.dialers.reason.stats':public const string ACTIVITIES DIALERS TRACK IMPORTED = 'jiminny.activities.dialers.track.imported':public const string ACTIVITTES DIALERS TRACK DURATION = 'jiminnv.activities.dialers.track.duration':public const strina AT AUTOMATION CRM WRITE EXECUTED = 'jiminnv.ai automation.com update executed':public const strina AUTOATED REPORTS = "imininy.automated_reports.usage';public const string ASK_JIMINNY_REPORTS = 'jiminny.ask_jiminny_reports.usage':public const string HUBSPOT_WEBHOOK_SYNC = 'jiminny.crm.hubspot_webhook_sync';Accent Reiect7usagespublic const string AI_ACTIVITY_TYPE = 'jiminny.ai_activity_type':public const string AI_CALL_SCORING = 'jiminny.ai_call_scoring':3 usagespublic const string WEBHOOK_DELIVERY_SUCCESS = 'jiminny.webhook.delivery.success';4 usagespublic const string WEBHOOK_DELIVERY_FAILED = 'jiminny.webhook.delivery.failed':s usagespublic const string ACTIVITY_PROCESSING STEP TIME = 'jiminny.activity.processing.step.time':+ 9 of 9 files →ore Il Don't ack adain (vecterdav 10-25)© ProviderRateLimiter.php= custom.logElaravel.log4 SF jiminny@localhost]A HS_local [jiminny@localhost]tiò accounts jiminny@localhost]& console [PRODi& console (EtmA console [STAGING] XTx: Autoy liminny020 A18 V.13 ^# id, vuid, type, provider, playbook category id, user id, lead id, contact id, account id, opportunity.# crm_contiquration1d, crm_ provider 1d, transcription1d.576571572574576trom activitles where crm_configuration_1d = 1 and type = 'conterence"*and crm orovider 1d is NOT NULLIand provider != 'uploader" and actual_start_time 1S NOT NULLORDER by id desc;select * from activities where id = 54747783: # 00U04000000CzoiMA0select p.id, p.activity_type, pc.id, pc.nameFROM playbooks pjoin playbook_categories pc 1<->l.n: on p.id = pc.playbook_idwhere p.team_id = 1 and p.activity_type = 'event';SELECT * FROM crm_fields WHERE crm_configuration_id = 1 and object_type = 'event';SELECT * FROM crm_field_values WHERE crm_field_id = 4;select * from crm_layouts cl join playbook_layouts pl 1<->1..n: on cl.id = pl.layout_idwhere crm_configuration_id = 1 and pl.playbook_id = 175;select x tron ceans.SELECT r.* FROM automated reports rjoin teams t on r.team id =t.idwhEre r.frequency = 'dallyAND t.status = 'active'AND (r.expires at >= now@) OR r.expires at IS NULL):select * from automated report results where renort 1d IN (18. 3501select * from activity searches where 1d = 109321select * from activity search filters where activity search id = 10932:orden hy id desciselect * from automated_report_results order by id desc;select * from automated_reports where id IN (55);select * from automated nenontnesults where id IN (81);select * from users where id IN (10633, 13987, 11985);select * from usens whene anoun id TN (3710)•SELECT * FROM automated_reports WHERE uuid_to_bin('18a06a75-afd2-476f-aadc-14d4057bdda2') = uuid›SELECT * FROM automated_report_results WHERE uuid_to_bin('582d4b50-8cd3-42a9-9819-d676ff8f3b43') = uuid;CascadeHubspot Rate LimitingHubspot Rate Limit Himinny.askeThought for 1sRead AutomatedReportsCallbackService.phpSearched isAskJiminnyReport in ~/jiminny/app (*.php) (9) >kead constants.ongThoughtsNow I'll add the new constant and uodate the pushToDatadoa method:•Constants.phpThinkingNow I need to update the pushtoDatadoa method• suppont Dally • In 3n 13m100% LzFri 8 May 11:47:59AskJiminnyReportActivityServiceTest vDatadoa Metric Cor+0 ..atedReport->isAskJiminnyReport() we should send it towAsk anvthina (*4L)+ @ CodelC° Adantive* Reiect alliiAccent alliO IDE error occurredSee details and submit reportWN Windsurf Toams 11-1/21 charc)UTE.8io 4 spaces...
|
7408
|
NULL
|
NULL
|
NULL
|
|
6426
|
276
|
4
|
2026-05-08T06:26:21.743640+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778221581743_m2.jpg...
|
Firefox
|
SevenShores\Hubspot\Exceptions\BadRequest: Client SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT — Work...
|
True
|
jiminny.sentry.io/issues/7007366572/?environment=p jiminny.sentry.io/issues/7007366572/?environment=production&environment=production-eu&project=82419&query=is%3Aunresolved&referrer=issue-stream&sort=freq...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app
Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app
Illuminate\Queue\MaxAttemptsExceededException: Jiminny\Jobs\Activity\DeleteTeamChurnData has been attempted too many times. — jiminny — app
Illuminate\Queue\MaxAttemptsExceededException: Jiminny\Jobs\Activity\DeleteTeamChurnData has been attempted too many times. — jiminny — app
Pull requests · jiminny/app
Pull requests · jiminny/app
Userpilot | Ask Jiminny Report Generated
Userpilot | Ask Jiminny Report Generated
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
Problem loading page
Problem loading page
Search the CRM - HubSpot docs
Search the CRM - HubSpot docs
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to main content
Skip to main content
Toggle organization menu
Issues
Issues
Explore
Explore
Dashboards
Dashboards
Monitors
Monitors
Settings
Settings
Try Business
What's New
Help
[EMAIL]
Issues
Expand
Feed
Feed
Errors & Outages
Errors & Outages
Breached Metrics
Breached Metrics
Warnings
Warnings
User Feedback
User Feedback
Autofix
Autofix
Recently Run
Recently Run
All Views
All Views
Configure
Alerts Moved
Alerts
Moved
Issues
Issues
View Project Details
APP-1EED
Ask Seer
Ask Seer
/
Give Feedback
SevenShores\Hubspot\Exceptions\BadRequest
View events
Events (total)
Users (90d)
Level: Error
Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...)
17K
0
Ongoing
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
Resolve
Resolve
More resolve options
Archive
Archive
Archive options
Subscribe
Share
More Actions
Priority
Modify issue priority
High
Assignee
Modify issue assignee
Lukas Kovalik
production, production-eu
production, production-eu
90D
90D
Add a search term
Add a search term
Close sidebar
Toggle graph series - Events
Events
17K
Toggle graph series - Users
Users
0
release 68% 874599
release
68%
874599
environment 92% production
environment
92%
production
server_name 5% 1afcc19ab21f
server_name
5%
1afcc19ab21f
correlation_id <1% d59f2a2d-61c7-491a-9859-b5d9aec02eac
correlation_id
<1%
d59f2a2d-61c7-491a-9859-b5d9aec02eac
View all tags
View all tags
Select issue content
Events
Previous Event
Next Event
First
First
First
Latest
Latest
Latest
Recommended
Recommended
View More Events
View More Events
Copy as
Copy as
ID: 31c8b6c9
18 hours ago
JSON
JSON
Highlights
Highlights
Stack Trace
Stack Trace
Trace
Trace
Tags
Tags
Context
Context
php
8.3.30
Linux
6.1.164-196.303.amzn2023.aarch64
882311
882311
production-eu
Collapse Highlights Section
Highlights
Edit
Edit
handled
yes
level
error
transaction
--
url
--
Trace: Trace ID
c27a4c3121bc4632967ef122e5360293
c27a4c3121bc4632967ef122e5360293
Collapse Stack Trace Section
Stack Trace
Display options
Display
Copy as
Copy as
There are 2 chained exceptions in this event.
SevenShores\Hubspot\Exceptions\BadRequest
SevenShores\Hubspot\Exceptions\BadRequest
SevenShores\Hubspot\Exceptions\BadRequest
Client error: `POST
https://api.hubapi.com/crm/v3/objects/contact/search
https://api.hubapi.com/crm/v3/objects/contact/search
` resulted in a `429 Too Many Requests` response:
{"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...)
mechanism
generic
handled
true
code
429
Crashed in non-app
:
/vendor/hubspot/hubspot-php/src/Exceptions/HubspotException.php
:24
in
SevenShores\Hubspot\Exceptions\HubspotException::create
Show 1 more frame
Show 1 more frame
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
:163
in
Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
In App
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
:51
in
Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::getPaginatedDataGenerator
In App
/app/Services/Crm/Hubspot/Client.php
:94
in
Jiminny\Services\Crm\Hubspot\Client::getPaginatedData
In App
/app/Services/Crm/Hubspot/Service.php
:1212
in
Jiminny\Services\Crm\Hubspot\Service::Jiminny\Services\Crm\Hubspot\{closure}
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Cache/Repository.php
:564
in
Illuminate\Cache\Repository::remember
Show 2 more frames
Show 2 more frames
/app/Services/Crm/Hubspot/Service.php
:1206
in
Jiminny\Services\Crm\Hubspot\Service::matchByName
In App
/app/Services/Crm/CachedCrmServiceDecorator.php
:167
in
Jiminny\Services\Crm\CachedCrmServiceDecorator::matchByName
In App
/app/Services/Crm/CrmActivityService.php
:227
in
Jiminny\Services\Crm\CrmActivityService::findCrmRecords
In App
/app/Services/Crm/CrmActivityService.php
:139
in
Jiminny\Services\Crm\CrmActivityService::updateParticipantsCrmData
In App
/app/Services/Crm/CrmActivityService.php
:81
in
Jiminny\Services\Crm\CrmActivityService::updateCrmData
In App
/app/Jobs/Crm/MatchActivityCrmData.php
:107
in
Jiminny\Jobs\Crm\MatchActivityCrmData::Jiminny\Jobs\Crm\{closure}
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php
:35
in
Illuminate\Database\Connection::transaction
/app/Jobs/Crm/MatchActivityCrmData.php
:87
in
Jiminny\Jobs\Crm\MatchActivityCrmData::handle
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php
:36
in
Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
Show 14 more frames
Show 14 more frames
/app/Queue/Worker/Worker.php
:71
in
Jiminny\Queue\Worker\Worker::process
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Queue/Worker.php
:435
in
Illuminate\Queue\Worker::runJob
Show 17 more frames
Show 17 more frames
GuzzleHttp\Exception\ClientException
GuzzleHttp\Exception\ClientException
GuzzleHttp\Exception\ClientException
Collapse Trace Preview Section
Trace Preview
View Full Trace
View Full Trace
0.00ms
5.56hr
11.11hr
16.67hr
22.22hr
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
1
hidden span
,
6
hidden issues
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`jiminny`.`activities`, CONSTRAINT `activities_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON UPDATE CASCADE) (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: update `activities` set `account_id` = 12170349, `contact_id` = 19474152, `activities`.`updated_at` = 2026-05-06 14:47:13 where `id` = 40573120) Illuminate\Database\QueryException /app/Models/Activity.php Jiminny\Models\Activity::updateActivityCrmData /app/Models/Activity.php in Jiminny\Models\Activity::updateActivityCrmData
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-003Sf00000S04yAIAR' for key 'contacts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `contacts` (`crm_provider_id`, `team_id`, `account_id`, `user_id`, `owner_id`, `name`, `title`, `email`, `country_code`, `phone`, `ext`, `mobile_phone`, `photo_path`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (003Sf00000S04yAIAR, 399, 4157188, 14447, 005Sf000004k3AHIAY, Sandra, ?, [EMAIL], ?, ?, ?, ?, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/003Sf00000S04yAIAR.png, 2025-10-31 11:19:28, 318, E�P�����Q
X���, 2026-05-07 10:17:47, 2026-05-07 10:17:47)) Illuminate\Database\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\Services\Crm\Salesforce\Service::importContact /app/Services/Crm/Salesforce/Service.php in Jiminny\Services\Crm\Salesforce\Service::importContact
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-0015J00000XpymlQAB' for key 'accounts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `accounts` (`crm_provider_id`, `team_id`, `user_id`, `owner_id`, `name`, `photo_path`, `industry`, `domain`, `phone`, `ext`, `country_code`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (0015J00000XpymlQAB, 399, 19173, 0055J000001ooqFQAQ, Ikano Bank, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/0015J00000XpymlQAB.png, Banking, bank.ikano, [PHONE], ?, SE, 2022-10-25 10:10:30, 318, E�!j1�XZ�./�, 2026-05-07 10:47:55, 2026-05-07 10:47:55)) Illuminate\Database\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\Services\Crm\Salesforce\Service::importAccount /app/Services/Crm/Salesforce/Service.php in Jiminny\Services\Crm\Salesforce\Service::importAccount
Error
—
Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...) SevenShores\Hubspot\Exceptions\BadRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
Collapse Section
Tags
All
All
Custom
Custom
Application
Application
Client
Client
Other
Other
correlation_id
7fb25500-e8c4-426b-a764-1e17359efd93
environment
production-eu
handled
yes
laravel_version
12.54.1
level
error...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.34773937,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.36103722,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.34773937,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.36103722,"top":0.09577015,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.41505983,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.34773937,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.36103722,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":4,"bounds":{"left":0.34773937,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app","depth":5,"bounds":{"left":0.36103722,"top":0.16121309,"width":0.17037898,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Illuminate\\Queue\\MaxAttemptsExceededException: Jiminny\\Jobs\\Activity\\DeleteTeamChurnData has been attempted too many times. — jiminny — app","depth":4,"bounds":{"left":0.34773937,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Illuminate\\Queue\\MaxAttemptsExceededException: Jiminny\\Jobs\\Activity\\DeleteTeamChurnData has been attempted too many times. — jiminny — app","depth":5,"bounds":{"left":0.36103722,"top":0.19393456,"width":0.2606383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"bounds":{"left":0.34773937,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"bounds":{"left":0.36103722,"top":0.22665602,"width":0.04537899,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Userpilot | Ask Jiminny Report Generated","depth":4,"bounds":{"left":0.34773937,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Userpilot | Ask Jiminny Report Generated","depth":5,"bounds":{"left":0.36103722,"top":0.25937748,"width":0.07164229,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"bounds":{"left":0.34773937,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"bounds":{"left":0.36103722,"top":0.29209897,"width":0.19331782,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Problem loading page","depth":4,"bounds":{"left":0.34773937,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Problem loading page","depth":5,"bounds":{"left":0.36103722,"top":0.32482043,"width":0.037898935,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Search the CRM - HubSpot docs","depth":4,"bounds":{"left":0.34773937,"top":0.3463687,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Search the CRM - HubSpot docs","depth":5,"bounds":{"left":0.36103722,"top":0.3575419,"width":0.05651596,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.34773937,"top":0.3790902,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.36103722,"top":0.39026338,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.35056517,"top":0.41340783,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.35056517,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.3615359,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.3726729,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.38380983,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.3949468,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to main content","depth":8,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to main content","depth":9,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Toggle organization menu","depth":11,"bounds":{"left":0.43417552,"top":0.059856344,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Issues","depth":12,"bounds":{"left":0.42869017,"top":0.09736632,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"bounds":{"left":0.43434176,"top":0.13048683,"width":0.010305851,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Explore","depth":12,"bounds":{"left":0.42869017,"top":0.14804469,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Explore","depth":14,"bounds":{"left":0.43351063,"top":0.1811652,"width":0.011968086,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Dashboards","depth":12,"bounds":{"left":0.42869017,"top":0.19872306,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dashboards","depth":14,"bounds":{"left":0.42985374,"top":0.23184358,"width":0.019281914,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Monitors","depth":12,"bounds":{"left":0.42869017,"top":0.24940144,"width":0.021609042,"height":0.05027933},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Monitors","depth":14,"bounds":{"left":0.4325133,"top":0.28252193,"width":0.013962766,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.42869017,"top":0.29968077,"width":0.021609042,"height":0.050678372},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.43267953,"top":0.33280128,"width":0.013630319,"height":0.009976057},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Try Business","depth":10,"bounds":{"left":0.43417552,"top":0.88667196,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"What's New","depth":10,"bounds":{"left":0.43417552,"top":0.9114126,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":10,"bounds":{"left":0.43417552,"top":0.93615323,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"lukas.kovalik@jiminny.com","depth":10,"bounds":{"left":0.43417552,"top":0.9680766,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Issues","depth":13,"bounds":{"left":0.39079124,"top":0.066640064,"width":0.014461436,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":13,"bounds":{"left":0.43633643,"top":0.061452515,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Feed","depth":15,"bounds":{"left":0.38746676,"top":0.10055866,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed","depth":17,"bounds":{"left":0.39178857,"top":0.10734238,"width":0.010638298,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Errors & Outages","depth":15,"bounds":{"left":0.38746676,"top":0.14046289,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Errors & Outages","depth":17,"bounds":{"left":0.39178857,"top":0.14724661,"width":0.03673537,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Breached Metrics","depth":15,"bounds":{"left":0.38746676,"top":0.16759777,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Breached Metrics","depth":17,"bounds":{"left":0.39178857,"top":0.17438148,"width":0.037898935,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Warnings","depth":15,"bounds":{"left":0.38746676,"top":0.19473264,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Warnings","depth":17,"bounds":{"left":0.39178857,"top":0.20151636,"width":0.019946808,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"User Feedback","depth":15,"bounds":{"left":0.38746676,"top":0.22186752,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"User Feedback","depth":17,"bounds":{"left":0.39178857,"top":0.22865124,"width":0.032081116,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Autofix","depth":13,"bounds":{"left":0.38746676,"top":0.26177174,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Autofix","depth":16,"bounds":{"left":0.39145613,"top":0.26855546,"width":0.016289894,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Recently Run","depth":15,"bounds":{"left":0.38746676,"top":0.28731045,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Recently Run","depth":17,"bounds":{"left":0.39178857,"top":0.29409418,"width":0.028922873,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"All Views","depth":15,"bounds":{"left":0.38746676,"top":0.3272147,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All Views","depth":17,"bounds":{"left":0.39178857,"top":0.3339984,"width":0.019281914,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Configure","depth":14,"bounds":{"left":0.39145613,"top":0.3735036,"width":0.021941489,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Alerts Moved","depth":15,"bounds":{"left":0.38746676,"top":0.39225858,"width":0.058843084,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Alerts","depth":17,"bounds":{"left":0.39178857,"top":0.3990423,"width":0.012799202,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Moved","depth":17,"bounds":{"left":0.42819148,"top":0.39984038,"width":0.012466756,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Issues","depth":12,"bounds":{"left":0.45728058,"top":0.0650439,"width":0.013796543,"height":0.01556265},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Issues","depth":14,"bounds":{"left":0.45728058,"top":0.066640064,"width":0.013796543,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View Project Details","depth":13,"bounds":{"left":0.47772607,"top":0.06624102,"width":0.005319149,"height":0.012769354},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"APP-1EED","depth":16,"bounds":{"left":0.48570478,"top":0.066640064,"width":0.021941489,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Ask Seer","depth":10,"bounds":{"left":0.93484044,"top":0.059856344,"width":0.04720745,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Seer","depth":13,"bounds":{"left":0.9461436,"top":0.0650439,"width":0.019614361,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":14,"bounds":{"left":0.9740692,"top":0.065442935,"width":0.0021609042,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Give Feedback","depth":11,"bounds":{"left":0.9840425,"top":0.059856344,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View events","depth":13,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Events (total)","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Users (90d)","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Level: Error","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT\",\"correlationId\":\"019e024f-c (truncated...)","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17K","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ongoing","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService::executeSearchRequest","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Resolve","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Resolve","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More resolve options","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Archive","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Archive","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Archive options","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Subscribe","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Share","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"More Actions","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Priority","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Modify issue priority","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"High","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Assignee","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Modify issue assignee","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Lukas Kovalik","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"production, production-eu","depth":13,"on_screen":false,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"production, production-eu","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"90D","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"90D","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXComboBox","text":"Add a search term","depth":16,"on_screen":false,"help_text":"","placeholder":"Filter events…","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXComboBox","text":"Add a search term","depth":16,"on_screen":false,"help_text":"","placeholder":"Filter events…","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close sidebar","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Toggle graph series - Events","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Events","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17K","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Toggle graph series - Users","depth":12,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Users","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"release 68% 874599","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"release","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"68%","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"874599","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"environment 92% production","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"environment","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"92%","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"production","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"server_name 5% 1afcc19ab21f","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"server_name","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5%","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1afcc19ab21f","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"correlation_id <1% d59f2a2d-61c7-491a-9859-b5d9aec02eac","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"correlation_id","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"<1%","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"d59f2a2d-61c7-491a-9859-b5d9aec02eac","depth":14,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View all tags","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View all tags","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Select issue content","depth":13,"on_screen":false,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Events","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Previous Event","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Next Event","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"First","depth":14,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"First","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"First","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Latest","depth":14,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Latest","depth":15,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Latest","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Recommended","depth":14,"on_screen":false,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Recommended","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View More Events","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View More Events","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy as","depth":13,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Copy as","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ID: 31c8b6c9","depth":15,"bounds":{"left":0.4616024,"top":0.105347164,"width":0.029089095,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18 hours ago","depth":15,"bounds":{"left":0.5013298,"top":0.105347164,"width":0.027593086,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JSON","depth":14,"bounds":{"left":0.5337433,"top":0.10454908,"width":0.012300532,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JSON","depth":15,"bounds":{"left":0.5337433,"top":0.105347164,"width":0.012300532,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Highlights","depth":17,"bounds":{"left":0.7787567,"top":0.100159615,"width":0.024268618,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Highlights","depth":19,"bounds":{"left":0.78141624,"top":0.10614525,"width":0.018949468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Stack Trace","depth":17,"bounds":{"left":0.80369014,"top":0.100159615,"width":0.026928192,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Stack Trace","depth":19,"bounds":{"left":0.80634975,"top":0.10614525,"width":0.021609042,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Trace","depth":17,"bounds":{"left":0.8312833,"top":0.100159615,"width":0.015292553,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trace","depth":19,"bounds":{"left":0.83394283,"top":0.10614525,"width":0.009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Tags","depth":17,"bounds":{"left":0.8472407,"top":0.100159615,"width":0.013962766,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Tags","depth":19,"bounds":{"left":0.84990025,"top":0.10614525,"width":0.008643617,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Context","depth":17,"bounds":{"left":0.8618683,"top":0.100159615,"width":0.020113032,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Context","depth":19,"bounds":{"left":0.86452794,"top":0.10614525,"width":0.014793883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"php","depth":16,"bounds":{"left":0.46958113,"top":0.0,"width":0.00831117,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8.3.30","depth":16,"bounds":{"left":0.47988698,"top":0.0,"width":0.013962766,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Linux","depth":16,"bounds":{"left":0.5071476,"top":0.0,"width":0.011801862,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6.1.164-196.303.amzn2023.aarch64","depth":16,"bounds":{"left":0.5209442,"top":0.0,"width":0.076961435,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"882311","depth":17,"bounds":{"left":0.61053854,"top":0.0,"width":0.015458777,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"882311","depth":18,"bounds":{"left":0.61053854,"top":0.0,"width":0.015458777,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"production-eu","depth":17,"bounds":{"left":0.63863033,"top":0.0,"width":0.031416222,"height":0.012370312},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Highlights Section","depth":14,"bounds":{"left":0.4616024,"top":0.0,"width":0.39744017,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Highlights","depth":17,"bounds":{"left":0.47024602,"top":0.0043894653,"width":0.026595745,"height":0.01396648},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit","depth":14,"bounds":{"left":0.86170214,"top":0.0,"width":0.018949468,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Edit","depth":16,"bounds":{"left":0.8703458,"top":0.0059856344,"width":0.0076462766,"height":0.010774142},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handled","depth":16,"bounds":{"left":0.47024602,"top":0.033918597,"width":0.016788565,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yes","depth":16,"bounds":{"left":0.53174865,"top":0.033918597,"width":0.0071476065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"level","depth":16,"bounds":{"left":0.47024602,"top":0.051476456,"width":0.011968086,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"error","depth":16,"bounds":{"left":0.53174865,"top":0.051476456,"width":0.011968086,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"transaction","depth":16,"bounds":{"left":0.47024602,"top":0.069034316,"width":0.026263298,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":16,"bounds":{"left":0.53174865,"top":0.069034316,"width":0.0048204786,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"url","depth":16,"bounds":{"left":0.6828458,"top":0.033918597,"width":0.0071476065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"--","depth":16,"bounds":{"left":0.7443484,"top":0.033918597,"width":0.004654255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Trace: Trace ID","depth":16,"bounds":{"left":0.6821808,"top":0.051476456,"width":0.035904255,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"c27a4c3121bc4632967ef122e5360293","depth":16,"bounds":{"left":0.7443484,"top":0.051476456,"width":0.07646277,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"c27a4c3121bc4632967ef122e5360293","depth":17,"bounds":{"left":0.7443484,"top":0.051476456,"width":0.07646277,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Stack Trace Section","depth":14,"bounds":{"left":0.4616024,"top":0.1245012,"width":0.35206118,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Stack Trace","depth":17,"bounds":{"left":0.47024602,"top":0.132083,"width":0.030086435,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Display options","depth":15,"bounds":{"left":0.81632316,"top":0.12769353,"width":0.030585106,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Display","depth":17,"bounds":{"left":0.8249667,"top":0.13288109,"width":0.013962766,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy as","depth":14,"bounds":{"left":0.84890294,"top":0.12769353,"width":0.03174867,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Copy as","depth":16,"bounds":{"left":0.85754657,"top":0.13288109,"width":0.01512633,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"There are 2 chained exceptions in this event.","depth":16,"bounds":{"left":0.47024602,"top":0.16161214,"width":0.0965758,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest","depth":15,"bounds":{"left":0.47024602,"top":0.1963288,"width":0.40774602,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXHeading","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest","depth":17,"bounds":{"left":0.4788896,"top":0.20391062,"width":0.107546546,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest","depth":18,"bounds":{"left":0.4788896,"top":0.20430966,"width":0.107546546,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Client error: `POST","depth":17,"bounds":{"left":0.4788896,"top":0.23144454,"width":0.051861703,"height":0.014365523},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"https://api.hubapi.com/crm/v3/objects/contact/search","depth":17,"bounds":{"left":0.53075135,"top":0.23144454,"width":0.14045878,"height":0.014365523},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"https://api.hubapi.com/crm/v3/objects/contact/search","depth":18,"bounds":{"left":0.53075135,"top":0.23144454,"width":0.13480718,"height":0.014365523},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"` resulted in a `429 Too Many Requests` response:\n{\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT\",\"correlationId\":\"019e024f-c (truncated...)","depth":17,"bounds":{"left":0.4788896,"top":0.23144454,"width":0.35006648,"height":0.029130088},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"mechanism","depth":16,"bounds":{"left":0.48188165,"top":0.27494013,"width":0.021609042,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"generic","depth":17,"bounds":{"left":0.5091423,"top":0.273743,"width":0.016788565,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handled","depth":16,"bounds":{"left":0.53457445,"top":0.27494013,"width":0.014960106,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"true","depth":17,"bounds":{"left":0.55485374,"top":0.273743,"width":0.009474734,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"code","depth":16,"bounds":{"left":0.57297206,"top":0.27494013,"width":0.009142287,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"429","depth":17,"bounds":{"left":0.58776593,"top":0.273743,"width":0.0071476065,"height":0.0131683955},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Crashed in non-app","depth":20,"bounds":{"left":0.48188165,"top":0.31085396,"width":0.03673537,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":20,"bounds":{"left":0.51861703,"top":0.31085396,"width":0.0009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/vendor/hubspot/hubspot-php/src/Exceptions/HubspotException.php","depth":20,"bounds":{"left":0.5202792,"top":0.31085396,"width":0.13297872,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":24","depth":20,"bounds":{"left":0.65325797,"top":0.31085396,"width":0.005817819,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.66107047,"top":0.30965683,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\HubspotException::create","depth":20,"bounds":{"left":0.6675532,"top":0.31085396,"width":0.1143617,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show 1 more frame","depth":18,"bounds":{"left":0.8238032,"top":0.30686352,"width":0.04055851,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show 1 more frame","depth":21,"bounds":{"left":0.82579786,"top":0.31085396,"width":0.03656915,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php","depth":20,"bounds":{"left":0.48188165,"top":0.34197924,"width":0.13580452,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":163","depth":20,"bounds":{"left":0.61768615,"top":0.34197924,"width":0.007480053,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.6271609,"top":0.34078214,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService::executeSearchRequest","depth":20,"bounds":{"left":0.6334774,"top":0.34197924,"width":0.1783577,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.34197924,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php","depth":20,"bounds":{"left":0.48188165,"top":0.37470073,"width":0.13580452,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":51","depth":20,"bounds":{"left":0.61768615,"top":0.37470073,"width":0.004986702,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.6246675,"top":0.3735036,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService::getPaginatedDataGenerator","depth":20,"bounds":{"left":0.63098407,"top":0.37470073,"width":0.18733378,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.37470073,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Client.php","depth":20,"bounds":{"left":0.48188165,"top":0.40742218,"width":0.074634306,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":94","depth":20,"bounds":{"left":0.55651593,"top":0.40742218,"width":0.005817819,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.56432843,"top":0.40622506,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\Hubspot\\Client::getPaginatedData","depth":20,"bounds":{"left":0.57081115,"top":0.40742218,"width":0.10804521,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.40742218,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Service.php","depth":20,"bounds":{"left":0.48188165,"top":0.44014364,"width":0.077792555,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":1212","depth":20,"bounds":{"left":0.5596742,"top":0.44014364,"width":0.009142287,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.57081115,"top":0.43894652,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\Hubspot\\Service::Jiminny\\Services\\Crm\\Hubspot\\{closure}","depth":20,"bounds":{"left":0.57712764,"top":0.44014364,"width":0.15525267,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.44014364,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Called from","depth":20,"bounds":{"left":0.48188165,"top":0.47126895,"width":0.021276595,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":20,"bounds":{"left":0.5031583,"top":0.47126895,"width":0.0009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/vendor/laravel/framework/src/Illuminate/Cache/Repository.php","depth":20,"bounds":{"left":0.50482047,"top":0.47126895,"width":0.12184176,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":564","depth":20,"bounds":{"left":0.62666225,"top":0.47126895,"width":0.00831117,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.6369681,"top":0.47007182,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Cache\\Repository::remember","depth":20,"bounds":{"left":0.6434508,"top":0.47126895,"width":0.074634306,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show 2 more frames","depth":18,"bounds":{"left":0.8209774,"top":0.46727854,"width":0.04338431,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show 2 more frames","depth":21,"bounds":{"left":0.82297206,"top":0.47126895,"width":0.03939495,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/Hubspot/Service.php","depth":20,"bounds":{"left":0.48188165,"top":0.50239426,"width":0.077792555,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":1206","depth":20,"bounds":{"left":0.5596742,"top":0.50239426,"width":0.009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.5716423,"top":0.5011971,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\Hubspot\\Service::matchByName","depth":20,"bounds":{"left":0.57795876,"top":0.50239426,"width":0.105053194,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.50239426,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/CachedCrmServiceDecorator.php","depth":20,"bounds":{"left":0.48188165,"top":0.5351157,"width":0.09990027,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":167","depth":20,"bounds":{"left":0.5817819,"top":0.5351157,"width":0.006981383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.59075797,"top":0.5339186,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\CachedCrmServiceDecorator::matchByName","depth":20,"bounds":{"left":0.5972407,"top":0.5351157,"width":0.12749335,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.5351157,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/CrmActivityService.php","depth":20,"bounds":{"left":0.48188165,"top":0.5678372,"width":0.08178192,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":227","depth":20,"bounds":{"left":0.56366354,"top":0.5678372,"width":0.0078125,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.5734708,"top":0.5666401,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\CrmActivityService::findCrmRecords","depth":20,"bounds":{"left":0.57978725,"top":0.5678372,"width":0.11170213,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.5678372,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/CrmActivityService.php","depth":20,"bounds":{"left":0.48188165,"top":0.60055864,"width":0.08178192,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":139","depth":20,"bounds":{"left":0.56366354,"top":0.60055864,"width":0.007480053,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.5731383,"top":0.59936154,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\CrmActivityService::updateParticipantsCrmData","depth":20,"bounds":{"left":0.579621,"top":0.60055864,"width":0.13380983,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.60055864,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Services/Crm/CrmActivityService.php","depth":20,"bounds":{"left":0.48188165,"top":0.6332801,"width":0.08178192,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":81","depth":20,"bounds":{"left":0.56366354,"top":0.6332801,"width":0.005319149,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.5709774,"top":0.632083,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Services\\Crm\\CrmActivityService::updateCrmData","depth":20,"bounds":{"left":0.5772939,"top":0.6332801,"width":0.111369684,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.6332801,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Jobs/Crm/MatchActivityCrmData.php","depth":20,"bounds":{"left":0.48188165,"top":0.6660016,"width":0.08178192,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":107","depth":20,"bounds":{"left":0.56366354,"top":0.6660016,"width":0.00731383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.57297206,"top":0.66480446,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Jobs\\Crm\\MatchActivityCrmData::Jiminny\\Jobs\\Crm\\{closure}","depth":20,"bounds":{"left":0.5794548,"top":0.6660016,"width":0.13480718,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.6660016,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Called from","depth":20,"bounds":{"left":0.48188165,"top":0.6971269,"width":0.021276595,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":20,"bounds":{"left":0.5031583,"top":0.6971269,"width":0.0009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php","depth":20,"bounds":{"left":0.50482047,"top":0.6971269,"width":0.16771941,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":35","depth":20,"bounds":{"left":0.6725399,"top":0.6971269,"width":0.005817819,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.6803524,"top":0.69592977,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Database\\Connection::transaction","depth":20,"bounds":{"left":0.6868351,"top":0.6971269,"width":0.084109046,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Jobs/Crm/MatchActivityCrmData.php","depth":20,"bounds":{"left":0.48188165,"top":0.7282522,"width":0.08178192,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":87","depth":20,"bounds":{"left":0.56366354,"top":0.7282522,"width":0.0056515955,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.57130986,"top":0.7270551,"width":0.0043218085,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Jobs\\Crm\\MatchActivityCrmData::handle","depth":20,"bounds":{"left":0.57762635,"top":0.7282522,"width":0.094082445,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.7282522,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Called from","depth":20,"bounds":{"left":0.48188165,"top":0.7593775,"width":0.021276595,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":20,"bounds":{"left":0.5031583,"top":0.7593775,"width":0.0009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php","depth":20,"bounds":{"left":0.50482047,"top":0.7593775,"width":0.13464096,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":36","depth":20,"bounds":{"left":0.63946146,"top":0.7593775,"width":0.005817819,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.64727396,"top":0.7581804,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}","depth":20,"bounds":{"left":0.6537567,"top":0.7593775,"width":0.124667555,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show 14 more frames","depth":18,"bounds":{"left":0.81914896,"top":0.75538707,"width":0.045212764,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show 14 more frames","depth":21,"bounds":{"left":0.8211436,"top":0.7593775,"width":0.041223403,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/app/Queue/Worker/Worker.php","depth":20,"bounds":{"left":0.48188165,"top":0.7905028,"width":0.059341755,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":71","depth":20,"bounds":{"left":0.5412234,"top":0.7905028,"width":0.004654255,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.54787236,"top":0.7893057,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Jiminny\\Queue\\Worker\\Worker::process","depth":20,"bounds":{"left":0.554355,"top":0.7905028,"width":0.07396942,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"In App","depth":19,"bounds":{"left":0.84973407,"top":0.7905028,"width":0.011968086,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Called from","depth":20,"bounds":{"left":0.48188165,"top":0.8216281,"width":0.021276595,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":","depth":20,"bounds":{"left":0.5031583,"top":0.8216281,"width":0.0009973404,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/vendor/laravel/framework/src/Illuminate/Queue/Worker.php","depth":20,"bounds":{"left":0.50482047,"top":0.8216281,"width":0.11469415,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":":435","depth":20,"bounds":{"left":0.61951464,"top":0.8216281,"width":0.008144947,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in","depth":20,"bounds":{"left":0.6296542,"top":0.820431,"width":0.004488032,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Illuminate\\Queue\\Worker::runJob","depth":20,"bounds":{"left":0.63613695,"top":0.8216281,"width":0.061668884,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Show 17 more frames","depth":18,"bounds":{"left":0.8194814,"top":0.8176377,"width":0.04488032,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show 17 more frames","depth":21,"bounds":{"left":0.82147604,"top":0.8216281,"width":0.04089096,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"GuzzleHttp\\Exception\\ClientException","depth":15,"bounds":{"left":0.47024602,"top":0.85833997,"width":0.40774602,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"GuzzleHttp\\Exception\\ClientException","depth":17,"bounds":{"left":0.4788896,"top":0.8659218,"width":0.087765954,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"GuzzleHttp\\Exception\\ClientException","depth":18,"bounds":{"left":0.4788896,"top":0.86632085,"width":0.087765954,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Trace Preview Section","depth":14,"bounds":{"left":0.4616024,"top":0.9142059,"width":0.38248006,"height":0.028731046},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Trace Preview","depth":17,"bounds":{"left":0.47024602,"top":0.9213887,"width":0.035904255,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"View Full Trace","depth":14,"bounds":{"left":0.84674203,"top":0.9173983,"width":0.033909574,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View Full Trace","depth":16,"bounds":{"left":0.8494016,"top":0.92218673,"width":0.028590426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0.00ms","depth":19,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.011635638,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5.56hr","depth":19,"bounds":{"left":0.71974736,"top":0.9688747,"width":0.010139627,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11.11hr","depth":19,"bounds":{"left":0.7647939,"top":0.9688747,"width":0.009640957,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16.67hr","depth":19,"bounds":{"left":0.80984044,"top":0.9688747,"width":0.011136968,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22.22hr","depth":19,"bounds":{"left":0.8547208,"top":0.9688747,"width":0.011968086,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0s","depth":18,"bounds":{"left":0.67486703,"top":0.9688747,"width":0.003656915,"height":0.008778931},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":21,"bounds":{"left":0.47523272,"top":0.9828412,"width":0.0016622341,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"hidden span","depth":21,"bounds":{"left":0.47789228,"top":0.9828412,"width":0.022772606,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":21,"bounds":{"left":0.5006649,"top":0.9828412,"width":0.0019946808,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":21,"bounds":{"left":0.50265956,"top":0.9828412,"width":0.002493351,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"hidden issues","depth":21,"bounds":{"left":0.50598407,"top":0.9828412,"width":0.025598405,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Error","depth":22,"bounds":{"left":0.49916887,"top":1.0,"width":0.009807181,"height":-0.0019952059},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":22,"bounds":{"left":0.5103058,"top":1.0,"width":0.0029920214,"height":-0.0019952059},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`jiminny`.`activities`, CONSTRAINT `activities_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON UPDATE CASCADE) (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: update `activities` set `account_id` = 12170349, `contact_id` = 19474152, `activities`.`updated_at` = 2026-05-06 14:47:13 where `id` = 40573120) Illuminate\\Database\\QueryException /app/Models/Activity.php Jiminny\\Models\\Activity::updateActivityCrmData /app/Models/Activity.php in Jiminny\\Models\\Activity::updateActivityCrmData","depth":22,"bounds":{"left":0.51462764,"top":1.0,"width":0.48537236,"height":-0.0019952059},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Error","depth":22,"bounds":{"left":0.49916887,"top":1.0,"width":0.009807181,"height":-0.021149278},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":22,"bounds":{"left":0.5103058,"top":1.0,"width":0.0029920214,"height":-0.021149278},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-003Sf00000S04yAIAR' for key 'contacts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `contacts` (`crm_provider_id`, `team_id`, `account_id`, `user_id`, `owner_id`, `name`, `title`, `email`, `country_code`, `phone`, `ext`, `mobile_phone`, `photo_path`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (003Sf00000S04yAIAR, 399, 4157188, 14447, 005Sf000004k3AHIAY, Sandra, ?, sandra@whataventure.com, ?, ?, ?, ?, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/003Sf00000S04yAIAR.png, 2025-10-31 11:19:28, 318, E\u0018�P�����Q\rX���, 2026-05-07 10:17:47, 2026-05-07 10:17:47)) Illuminate\\Database\\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\\Services\\Crm\\Salesforce\\Service::importContact /app/Services/Crm/Salesforce/Service.php in Jiminny\\Services\\Crm\\Salesforce\\Service::importContact","depth":22,"bounds":{"left":0.51462764,"top":1.0,"width":0.48537236,"height":-0.019952059},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Error","depth":22,"bounds":{"left":0.49916887,"top":1.0,"width":0.009807181,"height":-0.04030323},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":22,"bounds":{"left":0.5103058,"top":1.0,"width":0.0029920214,"height":-0.04030323},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-0015J00000XpymlQAB' for key 'accounts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `accounts` (`crm_provider_id`, `team_id`, `user_id`, `owner_id`, `name`, `photo_path`, `industry`, `domain`, `phone`, `ext`, `country_code`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (0015J00000XpymlQAB, 399, 19173, 0055J000001ooqFQAQ, Ikano Bank, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/0015J00000XpymlQAB.png, Banking, bank.ikano, +15164060922, ?, SE, 2022-10-25 10:10:30, 318, E�!\u001b\u001bj1�XZ\u0017�./�, 2026-05-07 10:47:55, 2026-05-07 10:47:55)) Illuminate\\Database\\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\\Services\\Crm\\Salesforce\\Service::importAccount /app/Services/Crm/Salesforce/Service.php in Jiminny\\Services\\Crm\\Salesforce\\Service::importAccount","depth":22,"bounds":{"left":0.51462764,"top":1.0,"width":0.48537236,"height":-0.03910613},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Error","depth":22,"bounds":{"left":0.49916887,"top":1.0,"width":0.009807181,"height":-0.059457302},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"—","depth":22,"bounds":{"left":0.5103058,"top":1.0,"width":0.0029920214,"height":-0.059457302},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT\",\"correlationId\":\"019e024f-c (truncated...) SevenShores\\Hubspot\\Exceptions\\BadRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService::executeSearchRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService::executeSearchRequest","depth":22,"bounds":{"left":0.51462764,"top":1.0,"width":0.48537236,"height":-0.059457302},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse Section","depth":14,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Tags","depth":17,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"All","depth":16,"on_screen":false,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Custom","depth":16,"on_screen":false,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Custom","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Application","depth":16,"on_screen":false,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Application","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Client","depth":16,"on_screen":false,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Client","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Other","depth":16,"on_screen":false,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Other","depth":18,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"correlation_id","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7fb25500-e8c4-426b-a764-1e17359efd93","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"environment","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"production-eu","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"handled","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"yes","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"laravel_version","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12.54.1","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"level","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"error","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8930312346290037131
|
-4153340122187512814
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 3 Q2 - Platform Team - Scrum Board Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app
Jy 20807 check various issues with stages by nikolaybiaivanov · Pull Request #12041 · jiminny/app
Illuminate\Queue\MaxAttemptsExceededException: Jiminny\Jobs\Activity\DeleteTeamChurnData has been attempted too many times. — jiminny — app
Illuminate\Queue\MaxAttemptsExceededException: Jiminny\Jobs\Activity\DeleteTeamChurnData has been attempted too many times. — jiminny — app
Pull requests · jiminny/app
Pull requests · jiminny/app
Userpilot | Ask Jiminny Report Generated
Userpilot | Ask Jiminny Report Generated
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
Problem loading page
Problem loading page
Search the CRM - HubSpot docs
Search the CRM - HubSpot docs
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to main content
Skip to main content
Toggle organization menu
Issues
Issues
Explore
Explore
Dashboards
Dashboards
Monitors
Monitors
Settings
Settings
Try Business
What's New
Help
[EMAIL]
Issues
Expand
Feed
Feed
Errors & Outages
Errors & Outages
Breached Metrics
Breached Metrics
Warnings
Warnings
User Feedback
User Feedback
Autofix
Autofix
Recently Run
Recently Run
All Views
All Views
Configure
Alerts Moved
Alerts
Moved
Issues
Issues
View Project Details
APP-1EED
Ask Seer
Ask Seer
/
Give Feedback
SevenShores\Hubspot\Exceptions\BadRequest
View events
Events (total)
Users (90d)
Level: Error
Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...)
17K
0
Ongoing
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
Resolve
Resolve
More resolve options
Archive
Archive
Archive options
Subscribe
Share
More Actions
Priority
Modify issue priority
High
Assignee
Modify issue assignee
Lukas Kovalik
production, production-eu
production, production-eu
90D
90D
Add a search term
Add a search term
Close sidebar
Toggle graph series - Events
Events
17K
Toggle graph series - Users
Users
0
release 68% 874599
release
68%
874599
environment 92% production
environment
92%
production
server_name 5% 1afcc19ab21f
server_name
5%
1afcc19ab21f
correlation_id <1% d59f2a2d-61c7-491a-9859-b5d9aec02eac
correlation_id
<1%
d59f2a2d-61c7-491a-9859-b5d9aec02eac
View all tags
View all tags
Select issue content
Events
Previous Event
Next Event
First
First
First
Latest
Latest
Latest
Recommended
Recommended
View More Events
View More Events
Copy as
Copy as
ID: 31c8b6c9
18 hours ago
JSON
JSON
Highlights
Highlights
Stack Trace
Stack Trace
Trace
Trace
Tags
Tags
Context
Context
php
8.3.30
Linux
6.1.164-196.303.amzn2023.aarch64
882311
882311
production-eu
Collapse Highlights Section
Highlights
Edit
Edit
handled
yes
level
error
transaction
--
url
--
Trace: Trace ID
c27a4c3121bc4632967ef122e5360293
c27a4c3121bc4632967ef122e5360293
Collapse Stack Trace Section
Stack Trace
Display options
Display
Copy as
Copy as
There are 2 chained exceptions in this event.
SevenShores\Hubspot\Exceptions\BadRequest
SevenShores\Hubspot\Exceptions\BadRequest
SevenShores\Hubspot\Exceptions\BadRequest
Client error: `POST
https://api.hubapi.com/crm/v3/objects/contact/search
https://api.hubapi.com/crm/v3/objects/contact/search
` resulted in a `429 Too Many Requests` response:
{"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...)
mechanism
generic
handled
true
code
429
Crashed in non-app
:
/vendor/hubspot/hubspot-php/src/Exceptions/HubspotException.php
:24
in
SevenShores\Hubspot\Exceptions\HubspotException::create
Show 1 more frame
Show 1 more frame
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
:163
in
Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
In App
/app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php
:51
in
Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::getPaginatedDataGenerator
In App
/app/Services/Crm/Hubspot/Client.php
:94
in
Jiminny\Services\Crm\Hubspot\Client::getPaginatedData
In App
/app/Services/Crm/Hubspot/Service.php
:1212
in
Jiminny\Services\Crm\Hubspot\Service::Jiminny\Services\Crm\Hubspot\{closure}
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Cache/Repository.php
:564
in
Illuminate\Cache\Repository::remember
Show 2 more frames
Show 2 more frames
/app/Services/Crm/Hubspot/Service.php
:1206
in
Jiminny\Services\Crm\Hubspot\Service::matchByName
In App
/app/Services/Crm/CachedCrmServiceDecorator.php
:167
in
Jiminny\Services\Crm\CachedCrmServiceDecorator::matchByName
In App
/app/Services/Crm/CrmActivityService.php
:227
in
Jiminny\Services\Crm\CrmActivityService::findCrmRecords
In App
/app/Services/Crm/CrmActivityService.php
:139
in
Jiminny\Services\Crm\CrmActivityService::updateParticipantsCrmData
In App
/app/Services/Crm/CrmActivityService.php
:81
in
Jiminny\Services\Crm\CrmActivityService::updateCrmData
In App
/app/Jobs/Crm/MatchActivityCrmData.php
:107
in
Jiminny\Jobs\Crm\MatchActivityCrmData::Jiminny\Jobs\Crm\{closure}
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php
:35
in
Illuminate\Database\Connection::transaction
/app/Jobs/Crm/MatchActivityCrmData.php
:87
in
Jiminny\Jobs\Crm\MatchActivityCrmData::handle
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php
:36
in
Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
Show 14 more frames
Show 14 more frames
/app/Queue/Worker/Worker.php
:71
in
Jiminny\Queue\Worker\Worker::process
In App
Called from
:
/vendor/laravel/framework/src/Illuminate/Queue/Worker.php
:435
in
Illuminate\Queue\Worker::runJob
Show 17 more frames
Show 17 more frames
GuzzleHttp\Exception\ClientException
GuzzleHttp\Exception\ClientException
GuzzleHttp\Exception\ClientException
Collapse Trace Preview Section
Trace Preview
View Full Trace
View Full Trace
0.00ms
5.56hr
11.11hr
16.67hr
22.22hr
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
0s
1
hidden span
,
6
hidden issues
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`jiminny`.`activities`, CONSTRAINT `activities_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON UPDATE CASCADE) (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: update `activities` set `account_id` = 12170349, `contact_id` = 19474152, `activities`.`updated_at` = 2026-05-06 14:47:13 where `id` = 40573120) Illuminate\Database\QueryException /app/Models/Activity.php Jiminny\Models\Activity::updateActivityCrmData /app/Models/Activity.php in Jiminny\Models\Activity::updateActivityCrmData
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-003Sf00000S04yAIAR' for key 'contacts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `contacts` (`crm_provider_id`, `team_id`, `account_id`, `user_id`, `owner_id`, `name`, `title`, `email`, `country_code`, `phone`, `ext`, `mobile_phone`, `photo_path`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (003Sf00000S04yAIAR, 399, 4157188, 14447, 005Sf000004k3AHIAY, Sandra, ?, [EMAIL], ?, ?, ?, ?, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/003Sf00000S04yAIAR.png, 2025-10-31 11:19:28, 318, E�P�����Q
X���, 2026-05-07 10:17:47, 2026-05-07 10:17:47)) Illuminate\Database\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\Services\Crm\Salesforce\Service::importContact /app/Services/Crm/Salesforce/Service.php in Jiminny\Services\Crm\Salesforce\Service::importContact
Error
—
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '318-0015J00000XpymlQAB' for key 'accounts_crm_configuration_id_crm_provider_id_unique' (Connection: mysql, Host: jiminny-db-eu-prod.c8yi8pam1xrs.eu-west-1.rds.amazonaws.com, Port: 3306, Database: jiminny, SQL: insert into `accounts` (`crm_provider_id`, `team_id`, `user_id`, `owner_id`, `name`, `photo_path`, `industry`, `domain`, `phone`, `ext`, `country_code`, `remotely_created_at`, `crm_configuration_id`, `uuid`, `updated_at`, `created_at`) values (0015J00000XpymlQAB, 399, 19173, 0055J000001ooqFQAQ, Ikano Bank, /ee5e2bcb-666e-4c81-893f-83b2f0d66e5d/avatars/0015J00000XpymlQAB.png, Banking, bank.ikano, [PHONE], ?, SE, 2022-10-25 10:10:30, 318, E�!j1�XZ�./�, 2026-05-07 10:47:55, 2026-05-07 10:47:55)) Illuminate\Database\UniqueConstraintViolationException /app/Services/Crm/Salesforce/Service.php Jiminny\Services\Crm\Salesforce\Service::importAccount /app/Services/Crm/Salesforce/Service.php in Jiminny\Services\Crm\Salesforce\Service::importAccount
Error
—
Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT","correlationId":"019e024f-c (truncated...) SevenShores\Hubspot\Exceptions\BadRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest /app/Services/Crm/Hubspot/Pagination/HubspotPaginationService.php in Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService::executeSearchRequest
Collapse Section
Tags
All
All
Custom
Custom
Application
Application
Client
Client
Other
Other
correlation_id
7fb25500-e8c4-426b-a764-1e17359efd93
environment
production-eu
handled
yes
laravel_version
12.54.1
level
error...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
14173
|
629
|
33
|
2026-05-09T17:02:47.295711+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-09/1778 /Users/lukas/.screenpipe/data/data/2026-05-09/1778346167295_m1.jpg...
|
Firefox
|
HTTP Strict Transport Security - Wikipedia — Perso HTTP Strict Transport Security - Wikipedia — Personal...
|
True
|
en.wikipedia.org/wiki/HTTP_Strict_Transport_Securi en.wikipedia.org/wiki/HTTP_Strict_Transport_Security...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Clo Pull requests · screenpipe/screenpipe · GitHub
Close tab
DNS / Nameservers | Hostinger
Close tab
Nginx Proxy Manager
Close tab
HTTP Strict Transport Security - Wikipedia
Close tab
Screenpipe — Archive
Close tab
SQLite Web: archive.db
Close tab
SQLite Web: db.sqlite
Close tab
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
Close tab
DXP4800PLUS-B5F8
Close tab
AFFiNE - All In One KnowledgeOS
Close tab
All docs · AFFiNE
Close tab
Payments Logger
Close tab
Your old PC can run Windows 11 in a VM, but not on bare metal - [EMAIL] - Gmail
Close tab
Location Logger
Close tab
Finance Hub
Close tab
Finance Hub
Close tab
Select: transactions - db - Adminer
Close tab
Claude Code | Claude Platform
Close tab
Claude
Close tab
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Close tab
Applications - Admin - authentik
Close tab
New Tab
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Customize sidebar
Jump to content
Jump to content
Main menu
Wikipedia The Free Encyclopedia
Search Wikipedia
Search
Donate
Donate
Create account
Create account
Log in
Log in
Contents
Contents
hide
(Top)
(Top)
Specification history
Specification history
HSTS mechanism overview
HSTS mechanism overview
Applicability
Applicability
Limitations
Limitations
Toggle Limitations subsection
Toggle Limitations subsection
Solutions with preload list
Solutions with preload list
Privacy issues
Privacy issues
Browser support
Browser support
Deployment best practices
Deployment best practices
See also
See also
References
References
External links
External links
HTTP Strict Transport Security
HTTP Strict Transport Security
Go to an article in another language. Available in 16 languages
Article
Article
Talk
Talk
Read
Read
Edit
Edit
View history
View history
Tools
Appearance
hide
Text
Small
Small
Standard
Standard
Large
Large
Width
Standard
Standard
Wide
Wide
Color
Automatic
Automatic
Light
Light
Dark
Dark
From Wikipedia, the free encyclopedia
HTTP Strict Transport Security
(
HSTS
) is a policy mechanism that helps to protect websites against
man-in-the-middle attacks
man-in-the-middle attacks
such as
protocol downgrade attacks
protocol downgrade attacks
[1]
[
1
]
and
cookie hijacking
cookie hijacking
. It allows
web servers
web servers
to declare that web browsers (or other complying
user agents
user agents
) should automatically interact with it using only
HTTPS
HTTPS
connections, which provide
Transport Layer Security
Transport Layer Security
(TLS/SSL), unlike the insecure
HTTP
HTTP
used alone. HSTS is an
IETF
IETF
standards
standards
track protocol and is specified in
RFC
RFC
6797
6797
.
The HSTS Policy is communicated by the server to the user agent via an HTTP response
header
header
field named
Strict-Transport-Security
. HSTS Policy specifies a period of time during which the
user agent
user agent
should only access the server in a secure fashion.
[2]
[
2
]
: §5.2
Websites using HSTS often do not accept clear text HTTP, either by rejecting connections over HTTP or systematically redirecting users to HTTPS (though this is not required by the specification). The consequence of this is that a user-agent not capable of doing TLS will not be able to connect to the site.
The protection normally only applies after a user has visited the site at least once, relying on the principle of "
trust on first use
trust on first use
". The way this protection works is that when a user entering or selecting an HTTP (not HTTPS) URL to the site, the client, such as a Web browser, will automatically upgrade to HTTPS without making an HTTP request, thereby preventing any HTTP man-in-the-middle attack from occurring. To counteract this problem, an HSTS preload list maintained by
Google Chrome
Google Chrome
and used by other major
web browsers
web browsers
is maintained. If a domain is on this list, the browser skips the initial request and encrypts all communication immediately.
[3]
[
3
]
Additional domains can be registered at no cost.
[4]
[
4
]
Specification history
Specification history
[
edit
edit
]
The HSTS specification was published as RFC 6797 on 19 November 2012 after being approved on 2 October 2012 by the
IESG
IESG
for publication as a
Proposed Standard
Proposed Standard
RFC
RFC
.
[5]
[
5
]
The authors originally submitted it as an
Internet Draft
Internet Draft
on 17 June 2010. With the conversion to an Internet Draft, the specification name was altered from "Strict Transport Security" (STS) to "HTTP Strict Transport Security", because the specification applies only to
HTTP
HTTP
.
[6]
[
6
]
The HTTP response header field defined in the HSTS specification however remains named "Strict-Transport-Security".
The last so-called "community version" of the then-named "STS" specification was published on 18 December 2009, with revisions based on community feedback.
[7]
[
7
]
The original draft specification by Jeff Hodges from
PayPal
PayPal
, Collin Jackson, and Adam Barth was published on 18 September 2009.
[8]
[
8
]
The HSTS specification is based on original work by Jackson and Barth as described in their paper "ForceHTTPS: Protecting High-Security Web Sites from Network Attacks".
[9]
[
9
]
Additionally, HSTS is the realization of one facet of an overall vision for improving web security, put forward by Jeff Hodges and Andy Steingruebl in their 2010 paper
The Need for Coherent Web Security Policy Framework(s)
.
[10]
[
10
]
HSTS mechanism overview
HSTS mechanism overview
[
edit
edit
]
A server implements an HSTS policy by supplying a header over an HTTPS connection (HSTS headers over HTTP are ignored).
[1]
[
1
]
For example, a server could send a header such that future requests to the domain for the next year (max-age is specified in seconds; 31,536,000 is equal to one non-leap year) use only HTTPS:
Strict-Transport-Security: max-age=31536000
.
When a web application issues HSTS Policy to user agents, conformant user agents behave as follows:
[2]
[
2
]
: §5
Automatically turn any insecure links referencing the web application into secure links (e.g.
http://example.com/some/page/
will be modified to
https://example.com/some/page/
before
accessing the server).
If the security of the connection cannot be ensured (e.g. the server's
TLS
TLS
certificate
certificate
is not trusted), the user agent must terminate the connection
[2]
[
2
]
: §8.4
and should not allow the user to access the web application.
[2]
[
2
]
: §12.1
This helps protect web application users against some passive (
eavesdropping
eavesdropping
) and active network
attacks
attacks
.
[2]
[
2
]
: §2.4
A
man-in-the-middle attacker
man-in-the-middle attacker
has a greatly reduced ability to intercept requests and responses between a user and a web application server while the user's browser has HSTS Policy in effect for that web application.
Applicability
Applicability
[
edit
edit
]
The most important security vulnerability that HSTS can fix is SSL-stripping
man-in-the-middle attacks
man-in-the-middle attacks
, first publicly introduced by
Moxie Marlinspike
Moxie Marlinspike
in his 2009 BlackHat Federal talk "New Tricks For Defeating SSL In Practice".
[11]
[
11
]
[12]
[
12
]
The SSL (and
TLS
TLS
) stripping attack works by transparently converting a secure
HTTPS
HTTPS
connection into a plain HTTP connection. The user can see that the connection is insecure, but crucially there is no way of knowing whether the connection
should
be secure. At the time of Marlinspike's talk, many websites did not use TLS/SSL, therefore there was no way of knowing (without prior knowledge) whether the use of plain HTTP was due to an attack, or simply because the website had not implemented TLS/SSL. Additionally, no warnings are presented to the user during the downgrade process, making the attack fairly subtle to all but the most vigilant. Marlinspike's sslstrip tool fully automates the attack.
[
citation needed
citation needed
]
HSTS addresses this problem
[2]
[
2
]
: §2.4
by informing the browser that connections to the site should always use TLS/SSL. The HSTS header can be stripped by the attacker if this is the user's first visit.
Google Chrome
Google Chrome
,
Mozilla Firefox
Mozilla Firefox
,
Internet Explorer
Internet Explorer
, and
Microsoft Edge
Microsoft Edge
attempt to limit this problem by including a "pre-loaded" list of HSTS sites.
[13]
[
13
]
[14]
[
14
]
[15]
[
15
]
Unfortunately this solution cannot scale to include all websites on the internet. See
limitations
limitations
, below.
HSTS can also help to prevent having one's cookie-based website login credentials stolen by widely available tools such as
Firesheep
Firesheep
.
[16]
[
16
]
Because HSTS is time limited, it is sensitive to attacks involving shifting the victim's computer time e.g. using false
NTP
NTP
packets.
[17]
[
17
]
Limitations
Limitations
[
edit
edit
]
The initial request remains unprotected from active attacks if it uses an insecure protocol such as plain HTTP or if the
URI
URI
for the initial request was obtained over an
insecure channel
insecure channel
.
[2]
[
2
]
: §14.6
The same applies to the first request after the activity period specified in the advertised HSTS Policy
max-age
(sites should set a period of several days or months depending on user activity and behavior).
Solutions with preload list
Solutions with preload list
[
edit
edit
]
Google Chrome
Google Chrome
,
Mozilla Firefox
Mozilla Firefox
, and
Internet Explorer
Internet Explorer
/
Microsoft Edge
Microsoft Edge
address this limitation by implementing a "HSTS preloaded list", which is a list that contains known sites supporting HSTS.
[18]
[
18
]
[13]
[
13
]
[14]
[
14
]
[15]
[
15
]
This list is distributed with the browser so that it uses HTTPS for the initial request to the listed sites as well. As previously mentioned, these pre-loaded lists cannot scale to cover the entire Web. A potential solution might be achieved by using
DNS
DNS
records to declare HSTS Policy, and accessing them securely via
DNSSEC
DNSSEC
, optionally with certificate fingerprints to ensure validity (which requires running a validating resolver to avoid
last mile
last mile
issues).
[19]
[
19
]
Junade Ali has noted that HSTS is ineffective against the use of false domains; by using DNS-based attacks, it is possible for a man-in-the-middle interceptor to serve traffic from an artificial domain which is not on the HSTS Preload list,
[20]
[
20
]
this can be made possible by DNS Spoofing Attacks,
[21]
[
21
]
or simply a domain name that misleadingly resembles the real domain name such as
www.example.org
instead of
www.example.com
.
Even with an HSTS preloaded list, HSTS cannot prevent advanced attacks against TLS itself, such as the
BEAST
BEAST
or
CRIME
CRIME
attacks introduced by Juliano Rizzo and Thai Duong. Attacks against TLS itself are
orthogonal
orthogonal
to HSTS policy enforcement. Neither can it protect against attacks on the server - if someone compromises it, it will happily serve any content over TLS.
Privacy issues
Privacy issues
[
edit
edit
]
HSTS can be used to near-indelibly tag visiting browsers with recoverable identifying data (
supercookies
supercookies
) which can persist in and out of browser "
incognito
incognito
" privacy modes. By creating a web page that makes multiple HTTP requests to selected domains, for example, if twenty browser requests to twenty different domains are used, theoretically over one million visitors can be distinguished (2
20
) due to the resulting requests arriving via HTTP vs. HTTPS; the latter being the previously recorded binary "bits" established earlier via HSTS headers.
[22]
[
22
]
Browser support
Browser support
[
edit
edit
]
Settings page Security within Chromium 45, showing the status of the security policy for the domain "en.wikipedia.org".
Chromium
Chromium
and
Google Chrome
Google Chrome
since version [IP_ADDRESS]
[23]
[
23
]
[24]
[
24
]
Firefox
Firefox
since version 4;
[1]
[
1
]
with Firefox 17,
Mozilla
Mozilla
integrates a list of websites supporting HSTS.
[14]
[
14
]
Opera
Opera...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Pull requests · screenpipe/screenpipe · GitHub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"DNS / Nameservers | Hostinger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Nginx Proxy Manager","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"HTTP Strict Transport Security - Wikipedia","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Screenpipe — Archive","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"SQLite Web: archive.db","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"SQLite Web: db.sqlite","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"DXP4800PLUS-B5F8","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"AFFiNE - All In One KnowledgeOS","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"All docs · AFFiNE","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Payments Logger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Your old PC can run Windows 11 in a VM, but not on bare metal - kovaliklukas@gmail.com - Gmail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Location Logger","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Finance Hub","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Select: transactions - db - Adminer","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Claude Code | Claude Platform","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Claude","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Applications - Admin - authentik","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Bitwarden","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Jump to content","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jump to content","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Main menu","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Wikipedia The Free Encyclopedia","depth":7,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXTextField","text":"Search Wikipedia","depth":11,"on_screen":true,"help_text":"Search Wikipedia [⌃⌥f]","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Donate","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Donate","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Create account","depth":11,"on_screen":true,"help_text":"You are encouraged to create an account and log in; however, it is not mandatory","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create account","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Log in","depth":11,"on_screen":true,"help_text":"You're encouraged to log in; however, it's not mandatory. [⌃⌥o]","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Log in","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Contents","depth":11,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Contents","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"hide","depth":11,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"(Top)","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"(Top)","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Specification history","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Specification history","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HSTS mechanism overview","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HSTS mechanism overview","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Applicability","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Applicability","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Limitations","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Limitations","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Toggle Limitations subsection","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Toggle Limitations subsection","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Solutions with preload list","depth":14,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Solutions with preload list","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Privacy issues","depth":14,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Privacy issues","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Browser support","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Browser support","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Deployment best practices","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Deployment best practices","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"See also","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"See also","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"References","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"References","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"External links","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"External links","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"HTTP Strict Transport Security","depth":8,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HTTP Strict Transport Security","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Go to an article in another language. Available in 16 languages","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Article","depth":12,"on_screen":true,"help_text":"View the content page [⌃⌥c]","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Article","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Talk","depth":12,"on_screen":true,"help_text":"Discuss improvements to the content page [⌃⌥t]","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Talk","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Read","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Read","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Edit","depth":12,"on_screen":true,"help_text":"Edit this page [⌃⌥e]","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Edit","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"View history","depth":12,"on_screen":true,"help_text":"Past revisions of this page [⌃⌥h]","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"View history","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Tools","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Appearance","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"hide","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Text","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Small","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Small","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Standard","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Standard","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Large","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Large","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Width","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Standard","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Standard","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Wide","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wide","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Color","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Automatic","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Automatic","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Light","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Light","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Dark","depth":15,"on_screen":true,"help_text":"","role_description":"radio button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Dark","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"From Wikipedia, the free encyclopedia","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HTTP Strict Transport Security","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HSTS","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") is a policy mechanism that helps to protect websites against","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"man-in-the-middle attacks","depth":10,"on_screen":true,"help_text":"Man-in-the-middle attack","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"man-in-the-middle attacks","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"such as","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"protocol downgrade attacks","depth":10,"on_screen":true,"help_text":"Protocol downgrade attack","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"protocol downgrade attacks","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[1]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"cookie hijacking","depth":10,"on_screen":true,"help_text":"Session hijacking","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"cookie hijacking","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". It allows","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"web servers","depth":10,"on_screen":true,"help_text":"Web server","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"web servers","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to declare that web browsers (or other complying","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"user agents","depth":10,"on_screen":true,"help_text":"User agent","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"user agents","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") should automatically interact with it using only","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HTTPS","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HTTPS","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"connections, which provide","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Transport Layer Security","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Transport Layer Security","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(TLS/SSL), unlike the insecure","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HTTP","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HTTP","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"used alone. HSTS is an","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"IETF","depth":10,"on_screen":true,"help_text":"Internet Engineering Task Force","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"IETF","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"standards","depth":10,"on_screen":true,"help_text":"Internet Standard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"standards","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"track protocol and is specified in","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"RFC","depth":10,"on_screen":true,"help_text":"RFC (identifier)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"RFC","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"6797","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"6797","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The HSTS Policy is communicated by the server to the user agent via an HTTP response","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"header","depth":10,"on_screen":true,"help_text":"List of HTTP header fields","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"header","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"field named","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Strict-Transport-Security","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":". HSTS Policy specifies a period of time during which the","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"user agent","depth":10,"on_screen":true,"help_text":"User agent","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"user agent","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should only access the server in a secure fashion.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §5.2","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Websites using HSTS often do not accept clear text HTTP, either by rejecting connections over HTTP or systematically redirecting users to HTTPS (though this is not required by the specification). The consequence of this is that a user-agent not capable of doing TLS will not be able to connect to the site.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The protection normally only applies after a user has visited the site at least once, relying on the principle of \"","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"trust on first use","depth":10,"on_screen":true,"help_text":"Trust on first use","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"trust on first use","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\". The way this protection works is that when a user entering or selecting an HTTP (not HTTPS) URL to the site, the client, such as a Web browser, will automatically upgrade to HTTPS without making an HTTP request, thereby preventing any HTTP man-in-the-middle attack from occurring. To counteract this problem, an HSTS preload list maintained by","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Google Chrome","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Chrome","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and used by other major","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"web browsers","depth":10,"on_screen":true,"help_text":"Web browser","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"web browsers","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is maintained. If a domain is on this list, the browser skips the initial request and encrypts all communication immediately.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[3]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Additional domains can be registered at no cost.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[4]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"4","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Specification history","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Specification history","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":true,"help_text":"Edit section: Specification history","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The HSTS specification was published as RFC 6797 on 19 November 2012 after being approved on 2 October 2012 by the","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"IESG","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"IESG","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for publication as a","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Proposed Standard","depth":10,"on_screen":true,"help_text":"Internet Standard","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Proposed Standard","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"RFC","depth":10,"on_screen":true,"help_text":"Request for Comments","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"RFC","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[5]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The authors originally submitted it as an","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Internet Draft","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Internet Draft","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"on 17 June 2010. With the conversion to an Internet Draft, the specification name was altered from \"Strict Transport Security\" (STS) to \"HTTP Strict Transport Security\", because the specification applies only to","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HTTP","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HTTP","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[6]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"6","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The HTTP response header field defined in the HSTS specification however remains named \"Strict-Transport-Security\".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The last so-called \"community version\" of the then-named \"STS\" specification was published on 18 December 2009, with revisions based on community feedback.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[7]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"7","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The original draft specification by Jeff Hodges from","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"PayPal","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"PayPal","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", Collin Jackson, and Adam Barth was published on 18 September 2009.","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[8]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"8","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The HSTS specification is based on original work by Jackson and Barth as described in their paper \"ForceHTTPS: Protecting High-Security Web Sites from Network Attacks\".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[9]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"9","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Additionally, HSTS is the realization of one facet of an overall vision for improving web security, put forward by Jeff Hodges and Andy Steingruebl in their 2010 paper","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The Need for Coherent Web Security Policy Framework(s)","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[10]","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"10","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"HSTS mechanism overview","depth":10,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HSTS mechanism overview","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":true,"help_text":"Edit section: HSTS mechanism overview","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"bounds":{"left":0.0,"top":0.0,"width":0.0027777778,"height":0.015},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"A server implements an HSTS policy by supplying a header over an HTTPS connection (HSTS headers over HTTP are ignored).","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[1]","depth":11,"bounds":{"left":0.30069444,"top":0.0,"width":0.010069445,"height":0.014444444},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.30069444,"top":0.0,"width":0.0024305556,"height":0.014444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":12,"bounds":{"left":0.303125,"top":0.0,"width":0.0052083335,"height":0.014444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.30833334,"top":0.0,"width":0.0024305556,"height":0.014444444},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"For example, a server could send a header such that future requests to the domain for the next year (max-age is specified in seconds; 31,536,000 is equal to one non-leap year) use only HTTPS:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Strict-Transport-Security: max-age=31536000","depth":11,"bounds":{"left":0.015972223,"top":0.031111112,"width":0.2875,"height":0.02111111},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.30694443,"top":0.033888888,"width":0.003125,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"When a web application issues HSTS Policy to user agents, conformant user agents behave as follows:","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":11,"bounds":{"left":0.17777778,"top":0.07722222,"width":0.010069445,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.17777778,"top":0.07722222,"width":0.0027777778,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":12,"bounds":{"left":0.18055555,"top":0.07722222,"width":0.0048611113,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.18541667,"top":0.07722222,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §5","depth":12,"bounds":{"left":0.18784723,"top":0.07722222,"width":0.013541667,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Automatically turn any insecure links referencing the web application into secure links (e.g.","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"http://example.com/some/page/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"will be modified to","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"https://example.com/some/page/","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"before","depth":12,"bounds":{"left":0.046527777,"top":0.15,"width":0.031597223,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"accessing the server).","depth":11,"bounds":{"left":0.078125,"top":0.15,"width":0.11180556,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"If the security of the connection cannot be ensured (e.g. the server's","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"TLS","depth":11,"bounds":{"left":0.042013887,"top":0.18055555,"width":0.02048611,"height":0.018333333},"on_screen":false,"help_text":"Transport Layer Security","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TLS","depth":12,"bounds":{"left":0.042013887,"top":0.18055555,"width":0.02048611,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"certificate","depth":11,"bounds":{"left":0.065625,"top":0.18055555,"width":0.047569446,"height":0.018333333},"on_screen":false,"help_text":"Public key certificate","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"certificate","depth":12,"bounds":{"left":0.065625,"top":0.18055555,"width":0.047569446,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"is not trusted), the user agent must terminate the connection","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §8.4","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and should not allow the user to access the web application.","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":12,"bounds":{"left":0.15625,"top":0.20611112,"width":0.009722223,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"bounds":{"left":0.15625,"top":0.20611112,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":13,"bounds":{"left":0.15868056,"top":0.20611112,"width":0.0048611113,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"bounds":{"left":0.16354166,"top":0.20611112,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §12.1","depth":13,"bounds":{"left":0.16597222,"top":0.20611112,"width":0.026041666,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This helps protect web application users against some passive (","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"eavesdropping","depth":10,"bounds":{"left":0.0,"top":0.24722221,"width":0.072916664,"height":0.018333333},"on_screen":false,"help_text":"Eavesdropping","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"eavesdropping","depth":11,"bounds":{"left":0.0,"top":0.24722221,"width":0.072916664,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") and active network","depth":10,"bounds":{"left":0.05625,"top":0.24722221,"width":0.10243055,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"attacks","depth":10,"bounds":{"left":0.15868056,"top":0.24722221,"width":0.035416666,"height":0.018333333},"on_screen":false,"help_text":"Attack (computing)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"attacks","depth":11,"bounds":{"left":0.15868056,"top":0.24722221,"width":0.035416666,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.19409722,"top":0.24722221,"width":0.003125,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":11,"bounds":{"left":0.19722222,"top":0.24388888,"width":0.009722223,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.19722222,"top":0.24388888,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":12,"bounds":{"left":0.19965278,"top":0.24388888,"width":0.0048611113,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.20451389,"top":0.24388888,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §2.4","depth":12,"bounds":{"left":0.20694445,"top":0.24388888,"width":0.021180555,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"A","depth":10,"bounds":{"left":0.228125,"top":0.24722221,"width":0.013541667,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"man-in-the-middle attacker","depth":10,"on_screen":false,"help_text":"Man-in-the-middle attack","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"man-in-the-middle attacker","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"has a greatly reduced ability to intercept requests and responses between a user and a web application server while the user's browser has HSTS Policy in effect for that web application.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Applicability","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Applicability","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":false,"help_text":"Edit section: Applicability","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The most important security vulnerability that HSTS can fix is SSL-stripping","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"man-in-the-middle attacks","depth":10,"bounds":{"left":0.041319445,"top":0.41666666,"width":0.12777779,"height":0.018333333},"on_screen":false,"help_text":"Man-in-the-middle attack","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"man-in-the-middle attacks","depth":11,"bounds":{"left":0.041319445,"top":0.41666666,"width":0.12777779,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", first publicly introduced by","depth":10,"bounds":{"left":0.16909721,"top":0.41666666,"width":0.13402778,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Moxie Marlinspike","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Moxie Marlinspike","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"in his 2009 BlackHat Federal talk \"New Tricks For Defeating SSL In Practice\".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[11]","depth":11,"bounds":{"left":0.14201389,"top":0.4422222,"width":0.014236111,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.14201389,"top":0.4422222,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"11","depth":12,"bounds":{"left":0.14444445,"top":0.4422222,"width":0.009375,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.15381944,"top":0.4422222,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[12]","depth":11,"bounds":{"left":0.15625,"top":0.4422222,"width":0.014930556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.15625,"top":0.4422222,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12","depth":12,"bounds":{"left":0.15868056,"top":0.4422222,"width":0.010069445,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.16875,"top":0.4422222,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The SSL (and","depth":10,"bounds":{"left":0.17118056,"top":0.44555557,"width":0.074652776,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"TLS","depth":10,"bounds":{"left":0.24583334,"top":0.44555557,"width":0.02048611,"height":0.018333333},"on_screen":false,"help_text":"Transport Layer Security","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TLS","depth":11,"bounds":{"left":0.24583334,"top":0.44555557,"width":0.02048611,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") stripping attack works by transparently converting a secure","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"HTTPS","depth":10,"bounds":{"left":0.0,"top":0.47444445,"width":0.036458332,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"HTTPS","depth":11,"bounds":{"left":0.0,"top":0.47444445,"width":0.036458332,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"connection into a plain HTTP connection. The user can see that the connection is insecure, but crucially there is no way of knowing whether the connection","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"should","depth":10,"bounds":{"left":0.098958336,"top":0.50333333,"width":0.03263889,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"be secure. At the time of Marlinspike's talk, many websites did not use TLS/SSL, therefore there was no way of knowing (without prior knowledge) whether the use of plain HTTP was due to an attack, or simply because the website had not implemented TLS/SSL. Additionally, no warnings are presented to the user during the downgrade process, making the attack fairly subtle to all but the most vigilant. Marlinspike's sslstrip tool fully automates the attack.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"citation needed","depth":11,"on_screen":false,"help_text":"Wikipedia:Citation needed","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"citation needed","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HSTS addresses this problem","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §2.4","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"by informing the browser that connections to the site should always use TLS/SSL. The HSTS header can be stripped by the attacker if this is the user's first visit.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Google Chrome","depth":10,"bounds":{"left":0.0,"top":0.6944444,"width":0.07847222,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Chrome","depth":11,"bounds":{"left":0.0,"top":0.6944444,"width":0.07847222,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":10,"bounds":{"left":0.077430554,"top":0.6944444,"width":0.00625,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Mozilla Firefox","depth":10,"bounds":{"left":0.083680555,"top":0.6944444,"width":0.07152778,"height":0.018333333},"on_screen":false,"help_text":"Firefox","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Mozilla Firefox","depth":11,"bounds":{"left":0.083680555,"top":0.6944444,"width":0.07152778,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":10,"bounds":{"left":0.15520833,"top":0.6944444,"width":0.00625,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Internet Explorer","depth":10,"bounds":{"left":0.16145833,"top":0.6944444,"width":0.08194444,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Internet Explorer","depth":11,"bounds":{"left":0.16145833,"top":0.6944444,"width":0.08194444,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":10,"bounds":{"left":0.24340278,"top":0.6944444,"width":0.027430555,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Microsoft Edge","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Microsoft Edge","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"attempt to limit this problem by including a \"pre-loaded\" list of HSTS sites.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[13]","depth":11,"bounds":{"left":0.059722222,"top":0.72,"width":0.014930556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.059722222,"top":0.72,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13","depth":12,"bounds":{"left":0.062152777,"top":0.72,"width":0.010069445,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.072222225,"top":0.72,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[14]","depth":11,"bounds":{"left":0.074652776,"top":0.72,"width":0.014930556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.074652776,"top":0.72,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"14","depth":12,"bounds":{"left":0.077083334,"top":0.72,"width":0.009722223,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.08680555,"top":0.72,"width":0.0027777778,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[15]","depth":11,"bounds":{"left":0.08958333,"top":0.72,"width":0.014583333,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.08958333,"top":0.72,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15","depth":12,"bounds":{"left":0.09201389,"top":0.72,"width":0.009722223,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.10173611,"top":0.72,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Unfortunately this solution cannot scale to include all websites on the internet. See","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"limitations","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"limitations","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", below.","depth":10,"bounds":{"left":0.0,"top":0.75222224,"width":0.0375,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HSTS can also help to prevent having one's cookie-based website login credentials stolen by widely available tools such as","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Firesheep","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Firesheep","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[16]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"16","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Because HSTS is time limited, it is sensitive to attacks involving shifting the victim's computer time e.g. using false","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"NTP","depth":10,"bounds":{"left":0.23194444,"top":0.8744444,"width":0.022222223,"height":0.018333333},"on_screen":false,"help_text":"Network Time Protocol","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"NTP","depth":11,"bounds":{"left":0.23194444,"top":0.8744444,"width":0.022222223,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"packets.","depth":10,"bounds":{"left":0.25416666,"top":0.8744444,"width":0.044444446,"height":0.018333333},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[17]","depth":11,"bounds":{"left":0.2986111,"top":0.8711111,"width":0.014583333,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.2986111,"top":0.8711111,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"17","depth":12,"bounds":{"left":0.30104166,"top":0.8711111,"width":0.009722223,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.3107639,"top":0.8711111,"width":0.0024305556,"height":0.014444444},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Limitations","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Limitations","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":false,"help_text":"Edit section: Limitations","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The initial request remains unprotected from active attacks if it uses an insecure protocol such as plain HTTP or if the","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"URI","depth":10,"bounds":{"left":0.24722221,"top":0.9855555,"width":0.019097222,"height":0.01444447},"on_screen":false,"help_text":"Uniform Resource Identifier","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"URI","depth":11,"bounds":{"left":0.24722221,"top":0.9855555,"width":0.019097222,"height":0.01444447},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"for the initial request was obtained over an","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"insecure channel","depth":10,"on_screen":false,"help_text":"Insecure channel","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"insecure channel","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"bounds":{"left":0.0,"top":1.0,"width":0.003125,"height":-0.01444447},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[2]","depth":11,"bounds":{"left":0.0,"top":1.0,"width":0.009722223,"height":-0.01111114},"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"bounds":{"left":0.0,"top":1.0,"width":0.0024305556,"height":-0.01111114},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":12,"bounds":{"left":0.0,"top":1.0,"width":0.0048611113,"height":-0.01111114},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"bounds":{"left":0.0,"top":1.0,"width":0.0024305556,"height":-0.01111114},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":": §14.6","depth":12,"bounds":{"left":0.0,"top":1.0,"width":0.026041666,"height":-0.01111114},"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"The same applies to the first request after the activity period specified in the advertised HSTS Policy","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"max-age","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(sites should set a period of several days or months depending on user activity and behavior).","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Solutions with preload list","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Solutions with preload list","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":false,"help_text":"Edit section: Solutions with preload list","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Google Chrome","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Chrome","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":",","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Mozilla Firefox","depth":10,"on_screen":false,"help_text":"Firefox","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Mozilla Firefox","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", and","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Internet Explorer","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Internet Explorer","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Microsoft Edge","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Microsoft Edge","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"address this limitation by implementing a \"HSTS preloaded list\", which is a list that contains known sites supporting HSTS.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[18]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"18","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[13]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"13","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[14]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"14","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[15]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"15","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This list is distributed with the browser so that it uses HTTPS for the initial request to the listed sites as well. As previously mentioned, these pre-loaded lists cannot scale to cover the entire Web. A potential solution might be achieved by using","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"DNS","depth":10,"on_screen":false,"help_text":"Domain name system","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DNS","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"records to declare HSTS Policy, and accessing them securely via","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"DNSSEC","depth":10,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"DNSSEC","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", optionally with certificate fingerprints to ensure validity (which requires running a validating resolver to avoid","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"last mile","depth":10,"on_screen":false,"help_text":"Last mile (telecommunications)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"last mile","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"issues).","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[19]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"19","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Junade Ali has noted that HSTS is ineffective against the use of false domains; by using DNS-based attacks, it is possible for a man-in-the-middle interceptor to serve traffic from an artificial domain which is not on the HSTS Preload list,","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[20]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"this can be made possible by DNS Spoofing Attacks,","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[21]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"21","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or simply a domain name that misleadingly resembles the real domain name such as","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"www.example.org","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"instead of","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"www.example.com","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":".","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Even with an HSTS preloaded list, HSTS cannot prevent advanced attacks against TLS itself, such as the","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"BEAST","depth":10,"on_screen":false,"help_text":"BEAST (computer security)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"BEAST","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"or","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"CRIME","depth":10,"on_screen":false,"help_text":"CRIME (security exploit)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CRIME","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"attacks introduced by Juliano Rizzo and Thai Duong. Attacks against TLS itself are","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"orthogonal","depth":10,"on_screen":false,"help_text":"Orthogonal","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"orthogonal","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to HSTS policy enforcement. Neither can it protect against attacks on the server - if someone compromises it, it will happily serve any content over TLS.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Privacy issues","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Privacy issues","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":false,"help_text":"Edit section: Privacy issues","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"HSTS can be used to near-indelibly tag visiting browsers with recoverable identifying data (","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"supercookies","depth":10,"on_screen":false,"help_text":"HTTP cookie","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"supercookies","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") which can persist in and out of browser \"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"incognito","depth":10,"on_screen":false,"help_text":"Privacy mode","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"incognito","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"\" privacy modes. By creating a web page that makes multiple HTTP requests to selected domains, for example, if twenty browser requests to twenty different domains are used, theoretically over one million visitors can be distinguished (2","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"20","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":") due to the resulting requests arriving via HTTP vs. HTTPS; the latter being the previously recorded binary \"bits\" established earlier via HSTS headers.","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[22]","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"22","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Browser support","depth":10,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Browser support","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"edit","depth":10,"on_screen":false,"help_text":"Edit section: Browser support","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"edit","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":10,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings page Security within Chromium 45, showing the status of the security policy for the domain \"en.wikipedia.org\".","depth":10,"on_screen":false,"help_text":"Settings page for HTTPS Strict Transport Security within Chromium 45, showing the status of the security policy for the domain \"en.wikipedia.org\".","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Chromium","depth":11,"on_screen":false,"help_text":"Chromium (web browser)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Chromium","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"and","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Google Chrome","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Google Chrome","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"since version 4.0.211.0","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[23]","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"23","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[24]","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"24","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Firefox","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Firefox","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"since version 4;","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[1]","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"with Firefox 17,","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Mozilla","depth":11,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Mozilla","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"integrates a list of websites supporting HSTS.","depth":11,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"[14]","depth":12,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"14","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"]","depth":13,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Opera","depth":11,"on_screen":false,"help_text":"Opera (web browser)","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Opera","depth":12,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8928776463353420058
|
-8447421917114233760
|
click
|
accessibility
|
NULL
|
Pull requests · screenpipe/screenpipe · GitHub
Clo Pull requests · screenpipe/screenpipe · GitHub
Close tab
DNS / Nameservers | Hostinger
Close tab
Nginx Proxy Manager
Close tab
HTTP Strict Transport Security - Wikipedia
Close tab
Screenpipe — Archive
Close tab
SQLite Web: archive.db
Close tab
SQLite Web: db.sqlite
Close tab
screenpipe/.claude/skills at main · screenpipe/screenpipe · GitHub
Close tab
DXP4800PLUS-B5F8
Close tab
AFFiNE - All In One KnowledgeOS
Close tab
All docs · AFFiNE
Close tab
Payments Logger
Close tab
Your old PC can run Windows 11 in a VM, but not on bare metal - [EMAIL] - Gmail
Close tab
Location Logger
Close tab
Finance Hub
Close tab
Finance Hub
Close tab
Select: transactions - db - Adminer
Close tab
Claude Code | Claude Platform
Close tab
Claude
Close tab
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Close tab
Applications - Admin - authentik
Close tab
New Tab
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
Customize sidebar
Jump to content
Jump to content
Main menu
Wikipedia The Free Encyclopedia
Search Wikipedia
Search
Donate
Donate
Create account
Create account
Log in
Log in
Contents
Contents
hide
(Top)
(Top)
Specification history
Specification history
HSTS mechanism overview
HSTS mechanism overview
Applicability
Applicability
Limitations
Limitations
Toggle Limitations subsection
Toggle Limitations subsection
Solutions with preload list
Solutions with preload list
Privacy issues
Privacy issues
Browser support
Browser support
Deployment best practices
Deployment best practices
See also
See also
References
References
External links
External links
HTTP Strict Transport Security
HTTP Strict Transport Security
Go to an article in another language. Available in 16 languages
Article
Article
Talk
Talk
Read
Read
Edit
Edit
View history
View history
Tools
Appearance
hide
Text
Small
Small
Standard
Standard
Large
Large
Width
Standard
Standard
Wide
Wide
Color
Automatic
Automatic
Light
Light
Dark
Dark
From Wikipedia, the free encyclopedia
HTTP Strict Transport Security
(
HSTS
) is a policy mechanism that helps to protect websites against
man-in-the-middle attacks
man-in-the-middle attacks
such as
protocol downgrade attacks
protocol downgrade attacks
[1]
[
1
]
and
cookie hijacking
cookie hijacking
. It allows
web servers
web servers
to declare that web browsers (or other complying
user agents
user agents
) should automatically interact with it using only
HTTPS
HTTPS
connections, which provide
Transport Layer Security
Transport Layer Security
(TLS/SSL), unlike the insecure
HTTP
HTTP
used alone. HSTS is an
IETF
IETF
standards
standards
track protocol and is specified in
RFC
RFC
6797
6797
.
The HSTS Policy is communicated by the server to the user agent via an HTTP response
header
header
field named
Strict-Transport-Security
. HSTS Policy specifies a period of time during which the
user agent
user agent
should only access the server in a secure fashion.
[2]
[
2
]
: §5.2
Websites using HSTS often do not accept clear text HTTP, either by rejecting connections over HTTP or systematically redirecting users to HTTPS (though this is not required by the specification). The consequence of this is that a user-agent not capable of doing TLS will not be able to connect to the site.
The protection normally only applies after a user has visited the site at least once, relying on the principle of "
trust on first use
trust on first use
". The way this protection works is that when a user entering or selecting an HTTP (not HTTPS) URL to the site, the client, such as a Web browser, will automatically upgrade to HTTPS without making an HTTP request, thereby preventing any HTTP man-in-the-middle attack from occurring. To counteract this problem, an HSTS preload list maintained by
Google Chrome
Google Chrome
and used by other major
web browsers
web browsers
is maintained. If a domain is on this list, the browser skips the initial request and encrypts all communication immediately.
[3]
[
3
]
Additional domains can be registered at no cost.
[4]
[
4
]
Specification history
Specification history
[
edit
edit
]
The HSTS specification was published as RFC 6797 on 19 November 2012 after being approved on 2 October 2012 by the
IESG
IESG
for publication as a
Proposed Standard
Proposed Standard
RFC
RFC
.
[5]
[
5
]
The authors originally submitted it as an
Internet Draft
Internet Draft
on 17 June 2010. With the conversion to an Internet Draft, the specification name was altered from "Strict Transport Security" (STS) to "HTTP Strict Transport Security", because the specification applies only to
HTTP
HTTP
.
[6]
[
6
]
The HTTP response header field defined in the HSTS specification however remains named "Strict-Transport-Security".
The last so-called "community version" of the then-named "STS" specification was published on 18 December 2009, with revisions based on community feedback.
[7]
[
7
]
The original draft specification by Jeff Hodges from
PayPal
PayPal
, Collin Jackson, and Adam Barth was published on 18 September 2009.
[8]
[
8
]
The HSTS specification is based on original work by Jackson and Barth as described in their paper "ForceHTTPS: Protecting High-Security Web Sites from Network Attacks".
[9]
[
9
]
Additionally, HSTS is the realization of one facet of an overall vision for improving web security, put forward by Jeff Hodges and Andy Steingruebl in their 2010 paper
The Need for Coherent Web Security Policy Framework(s)
.
[10]
[
10
]
HSTS mechanism overview
HSTS mechanism overview
[
edit
edit
]
A server implements an HSTS policy by supplying a header over an HTTPS connection (HSTS headers over HTTP are ignored).
[1]
[
1
]
For example, a server could send a header such that future requests to the domain for the next year (max-age is specified in seconds; 31,536,000 is equal to one non-leap year) use only HTTPS:
Strict-Transport-Security: max-age=31536000
.
When a web application issues HSTS Policy to user agents, conformant user agents behave as follows:
[2]
[
2
]
: §5
Automatically turn any insecure links referencing the web application into secure links (e.g.
http://example.com/some/page/
will be modified to
https://example.com/some/page/
before
accessing the server).
If the security of the connection cannot be ensured (e.g. the server's
TLS
TLS
certificate
certificate
is not trusted), the user agent must terminate the connection
[2]
[
2
]
: §8.4
and should not allow the user to access the web application.
[2]
[
2
]
: §12.1
This helps protect web application users against some passive (
eavesdropping
eavesdropping
) and active network
attacks
attacks
.
[2]
[
2
]
: §2.4
A
man-in-the-middle attacker
man-in-the-middle attacker
has a greatly reduced ability to intercept requests and responses between a user and a web application server while the user's browser has HSTS Policy in effect for that web application.
Applicability
Applicability
[
edit
edit
]
The most important security vulnerability that HSTS can fix is SSL-stripping
man-in-the-middle attacks
man-in-the-middle attacks
, first publicly introduced by
Moxie Marlinspike
Moxie Marlinspike
in his 2009 BlackHat Federal talk "New Tricks For Defeating SSL In Practice".
[11]
[
11
]
[12]
[
12
]
The SSL (and
TLS
TLS
) stripping attack works by transparently converting a secure
HTTPS
HTTPS
connection into a plain HTTP connection. The user can see that the connection is insecure, but crucially there is no way of knowing whether the connection
should
be secure. At the time of Marlinspike's talk, many websites did not use TLS/SSL, therefore there was no way of knowing (without prior knowledge) whether the use of plain HTTP was due to an attack, or simply because the website had not implemented TLS/SSL. Additionally, no warnings are presented to the user during the downgrade process, making the attack fairly subtle to all but the most vigilant. Marlinspike's sslstrip tool fully automates the attack.
[
citation needed
citation needed
]
HSTS addresses this problem
[2]
[
2
]
: §2.4
by informing the browser that connections to the site should always use TLS/SSL. The HSTS header can be stripped by the attacker if this is the user's first visit.
Google Chrome
Google Chrome
,
Mozilla Firefox
Mozilla Firefox
,
Internet Explorer
Internet Explorer
, and
Microsoft Edge
Microsoft Edge
attempt to limit this problem by including a "pre-loaded" list of HSTS sites.
[13]
[
13
]
[14]
[
14
]
[15]
[
15
]
Unfortunately this solution cannot scale to include all websites on the internet. See
limitations
limitations
, below.
HSTS can also help to prevent having one's cookie-based website login credentials stolen by widely available tools such as
Firesheep
Firesheep
.
[16]
[
16
]
Because HSTS is time limited, it is sensitive to attacks involving shifting the victim's computer time e.g. using false
NTP
NTP
packets.
[17]
[
17
]
Limitations
Limitations
[
edit
edit
]
The initial request remains unprotected from active attacks if it uses an insecure protocol such as plain HTTP or if the
URI
URI
for the initial request was obtained over an
insecure channel
insecure channel
.
[2]
[
2
]
: §14.6
The same applies to the first request after the activity period specified in the advertised HSTS Policy
max-age
(sites should set a period of several days or months depending on user activity and behavior).
Solutions with preload list
Solutions with preload list
[
edit
edit
]
Google Chrome
Google Chrome
,
Mozilla Firefox
Mozilla Firefox
, and
Internet Explorer
Internet Explorer
/
Microsoft Edge
Microsoft Edge
address this limitation by implementing a "HSTS preloaded list", which is a list that contains known sites supporting HSTS.
[18]
[
18
]
[13]
[
13
]
[14]
[
14
]
[15]
[
15
]
This list is distributed with the browser so that it uses HTTPS for the initial request to the listed sites as well. As previously mentioned, these pre-loaded lists cannot scale to cover the entire Web. A potential solution might be achieved by using
DNS
DNS
records to declare HSTS Policy, and accessing them securely via
DNSSEC
DNSSEC
, optionally with certificate fingerprints to ensure validity (which requires running a validating resolver to avoid
last mile
last mile
issues).
[19]
[
19
]
Junade Ali has noted that HSTS is ineffective against the use of false domains; by using DNS-based attacks, it is possible for a man-in-the-middle interceptor to serve traffic from an artificial domain which is not on the HSTS Preload list,
[20]
[
20
]
this can be made possible by DNS Spoofing Attacks,
[21]
[
21
]
or simply a domain name that misleadingly resembles the real domain name such as
www.example.org
instead of
www.example.com
.
Even with an HSTS preloaded list, HSTS cannot prevent advanced attacks against TLS itself, such as the
BEAST
BEAST
or
CRIME
CRIME
attacks introduced by Juliano Rizzo and Thai Duong. Attacks against TLS itself are
orthogonal
orthogonal
to HSTS policy enforcement. Neither can it protect against attacks on the server - if someone compromises it, it will happily serve any content over TLS.
Privacy issues
Privacy issues
[
edit
edit
]
HSTS can be used to near-indelibly tag visiting browsers with recoverable identifying data (
supercookies
supercookies
) which can persist in and out of browser "
incognito
incognito
" privacy modes. By creating a web page that makes multiple HTTP requests to selected domains, for example, if twenty browser requests to twenty different domains are used, theoretically over one million visitors can be distinguished (2
20
) due to the resulting requests arriving via HTTP vs. HTTPS; the latter being the previously recorded binary "bits" established earlier via HSTS headers.
[22]
[
22
]
Browser support
Browser support
[
edit
edit
]
Settings page Security within Chromium 45, showing the status of the security policy for the domain "en.wikipedia.org".
Chromium
Chromium
and
Google Chrome
Google Chrome
since version [IP_ADDRESS]
[23]
[
23
]
[24]
[
24
]
Firefox
Firefox
since version 4;
[1]
[
1
]
with Firefox 17,
Mozilla
Mozilla
integrates a list of websites supporting HSTS.
[14]
[
14
]
Opera
Opera...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
24064
|
1008
|
5
|
2026-05-12T08:42:20.555401+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778575340555_m1.jpg...
|
Slack
|
support (Channel) - Jiminny Inc - 4 new items - Sl support (Channel) - Jiminny Inc - 4 new items - Slack...
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Petko Kashinski
Steliyan Georgiev
Galya Dimitrova
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev...
|
[{"role":"AXPopUpButton","text [{"role":"AXPopUpButton","text":"Switch workspaces… (Jiminny Inc) Has new messages","depth":14,"bounds":{"left":0.51180553,"top":0.08111111,"width":0.025,"height":0.04},"on_screen":true,"role_description":"pop-up button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"Home","depth":14,"bounds":{"left":0.50625,"top":0.14,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true,"is_expanded":false},{"role":"AXStaticText","text":"Home","depth":16,"bounds":{"left":0.5138889,"top":0.19222222,"width":0.020833334,"height":0.015555556},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"DMs","depth":14,"bounds":{"left":0.50625,"top":0.21555555,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"DMs","depth":16,"bounds":{"left":0.5159722,"top":0.26777777,"width":0.016666668,"height":0.015555556},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"Activity","depth":14,"bounds":{"left":0.50625,"top":0.2911111,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Activity","depth":16,"bounds":{"left":0.51111114,"top":0.34333333,"width":0.027083334,"height":0.015555556},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.51111114,"top":0.34333333,"width":0.0055555557,"height":0.015555556}},{"char_start":1,"char_count":7,"bounds":{"left":0.5159722,"top":0.34333333,"width":0.022222223,"height":0.015555556}}],"role_description":"text"},{"role":"AXRadioButton","text":"Files","depth":14,"bounds":{"left":0.50625,"top":0.36666667,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Files","depth":16,"bounds":{"left":0.51666665,"top":0.4188889,"width":0.015972223,"height":0.015555556},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.51666665,"top":0.4188889,"width":0.004166667,"height":0.015555556}},{"char_start":1,"char_count":4,"bounds":{"left":0.5208333,"top":0.4188889,"width":0.011805556,"height":0.015555556}}],"role_description":"text"},{"role":"AXRadioButton","text":"Later","depth":14,"bounds":{"left":0.50625,"top":0.4422222,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Later","depth":16,"bounds":{"left":0.5152778,"top":0.49444443,"width":0.018055556,"height":0.015555556},"on_screen":true,"role_description":"text"},{"role":"AXRadioButton","text":"More…","depth":14,"bounds":{"left":0.50625,"top":0.5177778,"width":0.036111113,"height":0.075555556},"on_screen":true,"role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More","depth":16,"bounds":{"left":0.5152778,"top":0.57,"width":0.01875,"height":0.015555556},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Unreads","depth":21,"bounds":{"left":0.57708335,"top":0.12777779,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Threads","depth":21,"bounds":{"left":0.57708335,"top":0.12777779,"width":0.036805555,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Huddles","depth":21,"bounds":{"left":0.57708335,"top":0.12777779,"width":0.038194444,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Drafts & sent","depth":21,"bounds":{"left":0.57708335,"top":0.12777779,"width":0.06111111,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":21,"bounds":{"left":0.68472224,"top":0.12777779,"width":0.0055555557,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Directories","depth":21,"bounds":{"left":0.57708335,"top":0.12777779,"width":0.050694443,"height":0.0011111111},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-x-integration-app","depth":23,"bounds":{"left":0.58819443,"top":0.14,"width":0.09166667,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.14,"width":0.0027777778,"height":0.02}},{"char_start":1,"char_count":24,"bounds":{"left":0.59097224,"top":0.14,"width":0.11319444,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"platform-inner-team","depth":23,"bounds":{"left":0.58819443,"top":0.1711111,"width":0.093055554,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"ai-chapter","depth":23,"bounds":{"left":0.58819443,"top":0.24444444,"width":0.046527777,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"alerts","depth":23,"bounds":{"left":0.58819443,"top":0.27555555,"width":0.025694445,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.27555555,"width":0.0055555557,"height":0.02}},{"char_start":1,"char_count":5,"bounds":{"left":0.59375,"top":0.27555555,"width":0.019444445,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"backend","depth":23,"bounds":{"left":0.58819443,"top":0.30666667,"width":0.038194444,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"bugs","depth":23,"bounds":{"left":0.58819443,"top":0.33777776,"width":0.022222223,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"confusion-clinic","depth":23,"bounds":{"left":0.58819443,"top":0.36888888,"width":0.072222225,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.36888888,"width":0.0048611113,"height":0.02}},{"char_start":1,"char_count":15,"bounds":{"left":0.59305555,"top":0.36888888,"width":0.06736111,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"curiosity_lab","depth":23,"bounds":{"left":0.58819443,"top":0.4,"width":0.057638887,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.4,"width":0.0048611113,"height":0.02}},{"char_start":1,"char_count":12,"bounds":{"left":0.59305555,"top":0.4,"width":0.05277778,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"engineering","depth":23,"bounds":{"left":0.58819443,"top":0.4311111,"width":0.054166667,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"general","depth":23,"bounds":{"left":0.58819443,"top":0.46222222,"width":0.034027778,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"jiminny-bg","depth":23,"bounds":{"left":0.58819443,"top":0.49333334,"width":0.048611112,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"platform-tickets","depth":23,"bounds":{"left":0.58819443,"top":0.52444446,"width":0.072916664,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.52444446,"width":0.00625,"height":0.02}},{"char_start":1,"char_count":15,"bounds":{"left":0.59444445,"top":0.52444446,"width":0.06666667,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"product_launches","depth":23,"bounds":{"left":0.58819443,"top":0.5555556,"width":0.08055556,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"random","depth":23,"bounds":{"left":0.58819443,"top":0.58666664,"width":0.035416666,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"releases","depth":23,"bounds":{"left":0.58819443,"top":0.61777776,"width":0.036805555,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"sofia-office","depth":23,"bounds":{"left":0.58819443,"top":0.6488889,"width":0.05138889,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.6488889,"width":0.0048611113,"height":0.02}},{"char_start":1,"char_count":11,"bounds":{"left":0.59305555,"top":0.6488889,"width":0.045833334,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"support","depth":23,"bounds":{"left":0.58819443,"top":0.68,"width":0.036111113,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"thank-yous","depth":23,"bounds":{"left":0.58819443,"top":0.7111111,"width":0.05138889,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"the_people_of_jiminny","depth":23,"bounds":{"left":0.58819443,"top":0.74222225,"width":0.094444446,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.74222225,"width":0.004166667,"height":0.02}},{"char_start":1,"char_count":20,"bounds":{"left":0.5923611,"top":0.74222225,"width":0.09861111,"height":0.02}}],"role_description":"text"},{"role":"AXStaticText","text":"Petko Kashinski","depth":23,"bounds":{"left":0.58819443,"top":0.8155556,"width":0.072222225,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Steliyan Georgiev","depth":23,"bounds":{"left":0.58819443,"top":0.8466667,"width":0.07986111,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Galya Dimitrova","depth":23,"bounds":{"left":0.58819443,"top":0.87777776,"width":0.07361111,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Aneliya Angelova","depth":23,"bounds":{"left":0.58819443,"top":0.9088889,"width":0.07847222,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Stefka Stoyanova","depth":23,"bounds":{"left":0.58819443,"top":0.94,"width":0.079166666,"height":0.02},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Vasil Vasilev","depth":23,"bounds":{"left":0.58819443,"top":0.9711111,"width":0.055555556,"height":0.02},"on_screen":true,"lines":[{"char_start":0,"char_count":1,"bounds":{"left":0.58819443,"top":0.9711111,"width":0.00625,"height":0.02}},{"char_start":1,"char_count":12,"bounds":{"left":0.59444445,"top":0.9711111,"width":0.048611112,"height":0.02}}],"role_description":"text"}]...
|
8927240594866146077
|
-8138606308584440281
|
click
|
hybrid
|
NULL
|
Switch workspaces… (Jiminny Inc) Has new messages
Switch workspaces… (Jiminny Inc) Has new messages
Home
Home
DMs
DMs
Activity
Activity
Files
Files
Later
Later
More…
More
Unreads
Threads
Huddles
Drafts & sent
1
Directories
jiminny-x-integration-app
platform-inner-team
ai-chapter
alerts
backend
bugs
confusion-clinic
curiosity_lab
engineering
general
jiminny-bg
platform-tickets
product_launches
random
releases
sofia-office
support
thank-yous
the_people_of_jiminny
Petko Kashinski
Steliyan Georgiev
Galya Dimitrova
Aneliya Angelova
Stefka Stoyanova
Vasil Vasilev
SlackFileEditViewGoHistoryWindowHelpDOCKERDOCKER (-zsh)DEV (-zsh)O $2APP (-zsh)883EU ()-zshkibanaasticsearch"["type": "log""@timestamp""data"],'"pid":7,"message""el"2026-05-11T19:54:53Z""tags" : ["warning"'Unabletorevive connection: [URL_WITH_CREDENTIALS] : "2026-05-11T19:54:53Z", "tags" : ["warning"asticsearch""data"],"pid":7,: "No livingkibana{"type":"log"!connections"}ugins""licensing"],,"@timestamp" : "2026-05-11T19:54:53Z", "tags" : ["warning'"pid""message" : "Licensecould not be obtained fromasticsearchduetoError:connectionskibana@timestamp": "2026-05-11T19:54:54Z""tags" : ["error"ticsearch"[ConnectionError]: getaddrinfo ENOTFOUND elasticseelasticsearch: 9200"}"@timestamp""2026-05-11T19:54:54Z","tags" : ["warning", "elto revive connection: [URL_WITH_CREDENTIALS] : "2026-05-11T19:54:57Z" , "tags" : ["warning", "elasticsearch", "data"],"pid" :7,: "No livingconnections"}1 {"type": "log""@timestamp": "2026-05-11T19:54:57Z","tags" : ["error","taskManager""taskManager"], "pid" :7,"message" : "Failed to pollfor work: Error: NoLiving connections"}1 {"type" : "log""2026-05-11T19:54:59Z""tags" : ["error""message":"[ConnectionError]: getaddrinfo ENOTFOUND elasticseelasticsearch:9200"}{"type" : "log""@timestamp" : "2026-05-11T19:55: 00Z","tags" : ["warning"asticsearch","data"], "pid":7,'revive connection: [URL_WITH_CREDENTIALS] "No1 {"type" : "log".connections"}, "tags" : ["warning", "el"@timestamp" : "2026-05-11T19:55:00Z""tags" : ["error", "plug, "taskManager""taskManager"],"pid":7,"message": "Failed to poll for work: Error: NoLiving connections"}unexpected EOFkas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $•EDHomeDMsActivity→Jiminny ...& jiminny-x-integrati...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic¿ Support Daily • in 3h 18 m100% (8• Tue 12 May 11:42:20Describe what you are looking for# support8 34• Messages• Files• BookmarksMore v+We ran a large reindovina nf our search data forElastic Search oYesterday ~.nd, and it turned outsome old corrupted data blocked the process, andmassively delayed it.A few fixes were applied. I have increased thenumber of servers consuming activities, but it willstill take some time until all scheduled activities areprocessed and indexed.What that means - we were processing older data(cince lan 1st 26) huit thace artivities comnete withN31Customize navigation bar# sofia-office# support# thank-yous# the_people_of jimi...^ Direct messages&. Petko KashinskiRo Steliyan Georgiev. Galya Dimitrovae. Aneliya Angelovaa. Stefka Stoyanova€. Vasil VasilevToday ~Vasil Vasilev 11:38 AMAll EU data is reindexed.But it turned out that the US is still in progress. Itwas reported the indexing has finished over theweekend, but apparently it's not. l've increased thenumber of consumers, that should speed up theremaining data consumption. Some delays mayhappen until all data indexing is finished.Message #supportAa...
|
24063
|
NULL
|
NULL
|
NULL
|
|
26311
|
1090
|
34
|
2026-05-12T12:20:05.904596+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778588405904_m1.jpg...
|
Claude
|
Claude
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpalol§ Retro - Platform • in 1h 40 m100% (8•Tue 12 May 15:20:05T81sshDOCKER₴81DEV (-zsh)O 882APP (-zsh)883ec2-user@ip-10-30-129-...84-rw-r--r--1lukasstaff6392211 May09:42soundcore AeroClip(input)_2026-05-11_06-41-48.mp4lukasstaff1888411May09:42soundcore AeroClip-rw-r--r---rw-r--r---rw-r--r---rw-r--r--lukasstaff(input)_2026-05-11_06-42-18.mp41394211May09:43soundcore AeroClip(input)_2026-05-11_06-42-56.mp4111lukasstaff565011May09:44soundcore AeroClip(input)_2026-05-11_06-43-53.mp4lukasstaff462011May09:45soundcore AeroCliplukasstaff685111(input)_2026-05-11_06-44-38.mp4May09:45soundcore AeroClip(input)_2026-05-11_06-45-10.mp4lukasstaff110711May09:45soundcore AeroClip(input)_2026-05-11_06-45-22.mp4-rw-r--r---W-r=-r---rw-r--r---rw-r--r--lukasstaff1780011May19:18soundcore AeroClip(input)_2026-05-11_16-18-23.mp4lukasstafflukasstaff1273211May19:19soundcore AeroClip (input)_2026-05-11_16-18-54.mp4736111May19:19soundcore AeroClip (input)_2026-05-11_16-19-24.mp41lukasstaff1662211May19:20soundcore AeroClip(input)_2026-05-11_16-19-54.mp4lukasstaff15093611May19:21soundcore AeroClip-rw-r--r---rw-r--r---rw-r--r---rw-r--r--1lukasstaff13473211(input)_2026-05-11_16-20-29.mp4May19:21soundcore AeroClip(input)_2026-05-11_16-21-01.mp4lukasstaff2369011May19:22soundcore AeroClip(input)_2026-05-11_16-21-31.mp4lukasstaff1665111May19:22soundcore AeroCliplukasstaff692211May19:23(input)_2026-05-11_16-22-01.mp4soundcore AeroClip(input)_2026-05-11_16-22-31.mp4lukasstaff560311May19:23soundcore AeroClip (input)_2026-05-11_16-23-01.mp4-rw-r--r--lukasstaff4950911May19:24soundcore AeroClip(input)_2026-05-11_16-23-31.mp4-rw-r--r--1 lukasstaff34462 11 May 19:24 soundcore AeroClip (input)_2026-05-11_16-24-01.mp4lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/.screenpipe/data $ cd ..lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/.screenpipe $ nasAdm1n@DXP4800PLUS-B5F8:~$cd /volume1/screenpipe/Adm1n@DXP4800PLUS-B5F8:/volume1/screenpipes 1ltotal 26Gdrwxrwxrwx+ 1 rootroot410 May1215:15drwxr-xr-x1rootroot450Apr2519:39drwxrwxrwx+ 1Admin admin202 Apr2620:10drwxrwxrwx+ 1Admin admin298 May1013:46drwxrwxrwx+1Admin admin144 May909:41drwxrwxrwx+1Admin admin70 May1013:47drwxrwxrwx+1Admin admin164 Apr 1116:51drwxrwxrwx+1rootroot5.1K May1120:55-rwxrwxrwx+1rootroot31Apr1817:42-rwxrwxrwx+1Admin admin13G May1120:551Admin admin11G May1012:31-rwxrwxrwx+1Admin admin3.5G May1120:15-rwxrwxrwx+Admin admin32K May1205:48-rwxrwxrwx+Admin admin0 Apr2617:17-rwxrwxrwx+1Admin admin11K May1209:09-rwxrwxrwx+1Admin admin219Apr2419:33-rwxrwxrwx+1Admin admin0 Apr1317:21-rwxrwxrWx+1Admin admin8.4K May1215:15-rwxrwxrwx+1Admin admin32K May1120:48-rwxrwxrwx+1Admin admin20K May1013:06app_settings.jsonarchive.dbarchive.db-bakdb.sqlitedb.sqlite-shmdb.sqlite-wal.DS_Store•gitignorescreenpipe.dbscreenpipe_fts_migrate.shscreenpipe_sync.shscreenpipe_sync_updated.shAdm1n@DXP4800PLUS-B5F8:/volume1/screenpipes-zsh*5screenpipe"ssh...
|
NULL
|
8926855126347400529
|
NULL
|
click
|
ocr
|
NULL
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpalol§ Retro - Platform • in 1h 40 m100% (8•Tue 12 May 15:20:05T81sshDOCKER₴81DEV (-zsh)O 882APP (-zsh)883ec2-user@ip-10-30-129-...84-rw-r--r--1lukasstaff6392211 May09:42soundcore AeroClip(input)_2026-05-11_06-41-48.mp4lukasstaff1888411May09:42soundcore AeroClip-rw-r--r---rw-r--r---rw-r--r---rw-r--r--lukasstaff(input)_2026-05-11_06-42-18.mp41394211May09:43soundcore AeroClip(input)_2026-05-11_06-42-56.mp4111lukasstaff565011May09:44soundcore AeroClip(input)_2026-05-11_06-43-53.mp4lukasstaff462011May09:45soundcore AeroCliplukasstaff685111(input)_2026-05-11_06-44-38.mp4May09:45soundcore AeroClip(input)_2026-05-11_06-45-10.mp4lukasstaff110711May09:45soundcore AeroClip(input)_2026-05-11_06-45-22.mp4-rw-r--r---W-r=-r---rw-r--r---rw-r--r--lukasstaff1780011May19:18soundcore AeroClip(input)_2026-05-11_16-18-23.mp4lukasstafflukasstaff1273211May19:19soundcore AeroClip (input)_2026-05-11_16-18-54.mp4736111May19:19soundcore AeroClip (input)_2026-05-11_16-19-24.mp41lukasstaff1662211May19:20soundcore AeroClip(input)_2026-05-11_16-19-54.mp4lukasstaff15093611May19:21soundcore AeroClip-rw-r--r---rw-r--r---rw-r--r---rw-r--r--1lukasstaff13473211(input)_2026-05-11_16-20-29.mp4May19:21soundcore AeroClip(input)_2026-05-11_16-21-01.mp4lukasstaff2369011May19:22soundcore AeroClip(input)_2026-05-11_16-21-31.mp4lukasstaff1665111May19:22soundcore AeroCliplukasstaff692211May19:23(input)_2026-05-11_16-22-01.mp4soundcore AeroClip(input)_2026-05-11_16-22-31.mp4lukasstaff560311May19:23soundcore AeroClip (input)_2026-05-11_16-23-01.mp4-rw-r--r--lukasstaff4950911May19:24soundcore AeroClip(input)_2026-05-11_16-23-31.mp4-rw-r--r--1 lukasstaff34462 11 May 19:24 soundcore AeroClip (input)_2026-05-11_16-24-01.mp4lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/.screenpipe/data $ cd ..lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/.screenpipe $ nasAdm1n@DXP4800PLUS-B5F8:~$cd /volume1/screenpipe/Adm1n@DXP4800PLUS-B5F8:/volume1/screenpipes 1ltotal 26Gdrwxrwxrwx+ 1 rootroot410 May1215:15drwxr-xr-x1rootroot450Apr2519:39drwxrwxrwx+ 1Admin admin202 Apr2620:10drwxrwxrwx+ 1Admin admin298 May1013:46drwxrwxrwx+1Admin admin144 May909:41drwxrwxrwx+1Admin admin70 May1013:47drwxrwxrwx+1Admin admin164 Apr 1116:51drwxrwxrwx+1rootroot5.1K May1120:55-rwxrwxrwx+1rootroot31Apr1817:42-rwxrwxrwx+1Admin admin13G May1120:551Admin admin11G May1012:31-rwxrwxrwx+1Admin admin3.5G May1120:15-rwxrwxrwx+Admin admin32K May1205:48-rwxrwxrwx+Admin admin0 Apr2617:17-rwxrwxrwx+1Admin admin11K May1209:09-rwxrwxrwx+1Admin admin219Apr2419:33-rwxrwxrwx+1Admin admin0 Apr1317:21-rwxrwxrWx+1Admin admin8.4K May1215:15-rwxrwxrwx+1Admin admin32K May1120:48-rwxrwxrwx+1Admin admin20K May1013:06app_settings.jsonarchive.dbarchive.db-bakdb.sqlitedb.sqlite-shmdb.sqlite-wal.DS_Store•gitignorescreenpipe.dbscreenpipe_fts_migrate.shscreenpipe_sync.shscreenpipe_sync_updated.shAdm1n@DXP4800PLUS-B5F8:/volume1/screenpipes-zsh*5screenpipe"ssh...
|
26310
|
NULL
|
NULL
|
NULL
|
|
22548
|
972
|
39
|
2026-05-12T07:13:12.180402+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-12/1778 /Users/lukas/.screenpipe/data/data/2026-05-12/1778569992180_m1.jpg...
|
Firefox
|
planhat - Google Search — Work
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
LLM pricing overview with the most actual prices - Google Search
LLM pricing overview with the most actual prices - Google Search
planhat - Google Search
planhat - Google Search
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Show thinking
Gemini said
Gemini said
Here is a summary of the changes in the provided code diff:
AI Call Scoring Additions
AI Call Scoring Additions
Pre-computed Scores:
Added strict instructions to treat the
ai_call_score
as pre-computed data. The system must interpret and summarize the provided scores rather than inventing or recalculating them.
Context Breakdown:...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New Tab","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pull requests · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20773] User Pilot not receiving events on report generated - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20776] Automated report - sentry - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20776] Automated report - sentry - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"TypeError: League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"LLM pricing overview with the most actual prices - Google Search","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LLM pricing overview with the most actual prices - Google Search","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"planhat - Google Search","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"planhat - Google Search","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Close Google Gemini (⌃X)","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.0,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.019097222,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04236111,"top":0.0,"width":0.022222223,"height":0.035555556},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"AI Chat settings","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"WORK, Google Account: lukas.kovalik@jiminny.com","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Main menu","depth":12,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"New Chat","depth":12,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open menu for conversation actions.","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"Conversation with Gemini","depth":15,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation with Gemini","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy prompt","depth":21,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\\n\\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":21,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"You said","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\\n\\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.","depth":23,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Expand","depth":21,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Listen","depth":22,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Show thinking","depth":25,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Gemini said","depth":20,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Gemini said","depth":21,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Here is a summary of the changes in the provided code diff:","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"AI Call Scoring Additions","depth":23,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI Call Scoring Additions","depth":24,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Pre-computed Scores:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Added strict instructions to treat the","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"ai_call_score","depth":27,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"as pre-computed data. The system must interpret and summarize the provided scores rather than inventing or recalculating them.","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Context Breakdown:","depth":26,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
8926275643889037655
|
-2831770884858987630
|
visual_change
|
accessibility
|
NULL
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
JY-20625 | JY-20742 | MCP POC by yalokin-jiminny · Pull Request #12036 · jiminny/app
LLM pricing overview with the most actual prices - Google Search
LLM pricing overview with the most actual prices - Google Search
planhat - Google Search
planhat - Google Search
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Show thinking
Gemini said
Gemini said
Here is a summary of the changes in the provided code diff:
AI Call Scoring Additions
AI Call Scoring Additions
Pre-computed Scores:
Added strict instructions to treat the
ai_call_score
as pre-computed data. The system must interpret and summarize the provided scores rather than inventing or recalculating them.
Context Breakdown:...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
3079
|
119
|
45
|
2026-05-07T11:59:34.348096+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778155174348_m1.jpg...
|
PhpStorm
|
faVsco.js – Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","depth":4,"on_screen":true,"value":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"68","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8926141032220118948
|
6378759383152203876
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
3077
|
NULL
|
NULL
|
NULL
|
|
3080
|
120
|
51
|
2026-05-07T11:59:34.415611+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778155174415_m2.jpg...
|
PhpStorm
|
faVsco.js – Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.47140956,"height":0.9066241},"on_screen":true,"lines":[{"char_start":1774,"char_count":160,"bounds":{"left":0.53025264,"top":0.0,"width":0.41223404,"height":0.014365523}},{"char_start":1934,"char_count":326,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2260,"char_count":380,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2640,"char_count":321,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2961,"char_count":206,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":3167,"char_count":220,"bounds":{"left":0.53025264,"top":0.0015961692,"width":0.46974736,"height":0.014365523}},{"char_start":3387,"char_count":373,"bounds":{"left":0.53025264,"top":0.01915403,"width":0.46974736,"height":0.014365523}}],"value":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.4820479,"top":0.17478053,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"68","depth":4,"bounds":{"left":0.49202126,"top":0.17478053,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.5043218,"top":0.17478053,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.51396275,"top":0.17318435,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.5212766,"top":0.17318435,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8926141032220118948
|
6378759383152203876
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
3078
|
NULL
|
NULL
|
NULL
|
|
3082
|
120
|
53
|
2026-05-07T11:59:38.816927+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778155178816_m2.jpg...
|
PhpStorm
|
faVsco.js – Client.php
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","depth":4,"bounds":{"left":0.52859044,"top":0.0726257,"width":0.47140956,"height":0.9066241},"on_screen":true,"lines":[{"char_start":1774,"char_count":160,"bounds":{"left":0.53025264,"top":0.0,"width":0.41223404,"height":0.014365523}},{"char_start":1934,"char_count":326,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2260,"char_count":380,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2640,"char_count":321,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":2961,"char_count":206,"bounds":{"left":0.53025264,"top":0.0,"width":0.46974736,"height":0.014365523}},{"char_start":3167,"char_count":220,"bounds":{"left":0.53025264,"top":0.0015961692,"width":0.46974736,"height":0.014365523}},{"char_start":3387,"char_count":373,"bounds":{"left":0.53025264,"top":0.01915403,"width":0.46974736,"height":0.014365523}}],"value":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.4820479,"top":0.17478053,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"68","depth":4,"bounds":{"left":0.49202126,"top":0.17478053,"width":0.010305851,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.5043218,"top":0.17478053,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.51396275,"top":0.17318435,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.5212766,"top":0.17318435,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8926141032220118948
|
6378759383152203876
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
3081
|
NULL
|
NULL
|
NULL
|
|
3084
|
119
|
47
|
2026-05-07T11:59:43.876559+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778155183876_m1.jpg...
|
PhpStorm
|
faVsco.js – Client.php
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"on_screen":true,"help_text":"Git Branch: master","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","depth":4,"on_screen":true,"value":"[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"3cb37764-b6ec-4d4c-85b3-298d24411fec\",\"trace_id\":\"1387c33f-5f58-4299-a3da-9c3ad7e240fe\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"997b55b4-ed52-4426-95b2-3d79f48278ae\",\"trace_id\":\"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring start {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:13] local.NOTICE: Monitoring end {\"correlation_id\":\"1f069a13-4342-40ed-bf79-1472c9c60637\",\"trace_id\":\"1c2564a5-1d98-4061-819a-060f3143e672\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"92fadd4f-de00-4eae-8e71-26517f0e405c\",\"trace_id\":\"034b1cf6-05b1-455d-9d58-e7286ec06e25\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}\n[2026-05-07 11:59:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.727,\"memoryPeakAfterCommandInMB\":99.727} {\"correlation_id\":\"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc\",\"trace_id\":\"a7cdd06b-a602-49f9-a0f7-24736d4d641a\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"68","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot;\n\nuse HubSpot\\Client\\Crm\\Deals\\ApiException as DealApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\ApiException as ContactApiException;\nuse HubSpot\\Client\\Crm\\Companies\\ApiException as CompanyApiException;\nuse HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectWithAssociations as ContactsWithAssociations;\nuse HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectWithAssociations as CompaniesWithAssociations;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectWithAssociations as DealWithAssociations;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectInput;\nuse HubSpot\\Client\\Crm\\Objects\\Model\\SimplePublicObjectWithAssociations as ObjectWithAssociations;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\Error;\nuse HubSpot\\Client\\Crm\\Pipelines\\Model\\PipelineStage;\nuse HubSpot\\Client\\Crm\\Properties\\Model\\Property;\nuse HubSpot\\Discovery\\Discovery;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\RateLimitException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Services\\Crm\\BaseClient;\nuse Jiminny\\Services\\Crm\\Hubspot\\DTO\\Response\\Owner;\nuse Jiminny\\Services\\SocialAccountService;\nuse SevenShores\\Hubspot\\Exceptions\\BadRequest;\nuse SevenShores\\Hubspot\\Exceptions\\HubspotException;\nuse SevenShores\\Hubspot\\Factory;\nuse SevenShores\\Hubspot\\Http\\Response;\nuse Jiminny\\Services\\Crm\\Hubspot\\Pagination\\HubspotPaginationService;\nuse Throwable;\n\n/**\n * @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}\n */\nclass Client extends BaseClient implements HubspotClientInterface\n{\n public const string MIN_API_VERSION = '2';\n\n public const string BASE_URL = 'https://api.hubapi.com';\n\n public const int ASSOCIATIONS_BATCH_SIZE_LIMIT = 1000;\n\n private HubspotPaginationService $paginationService;\n private HubspotTokenManager $tokenManager;\n private ProviderRateLimiter $rateLimiter;\n\n public function __construct(\n SocialAccountService $socialAccountService,\n HubspotPaginationService $paginationService,\n HubspotTokenManager $tokenManager,\n ProviderRateLimiter $rateLimiter,\n ) {\n parent::__construct($socialAccountService);\n $this->paginationService = $paginationService;\n $this->tokenManager = $tokenManager;\n $this->rateLimiter = $rateLimiter;\n\n $this->setBaseUrl(self::BASE_URL);\n $this->setVersion(self::MIN_API_VERSION);\n }\n\n /**\n * Single entry point for every HubSpot API call. Enforces the per-portal\n * rate limit configured in the rate_limits table (morphed to the current\n * Configuration) and reacts to a real 429 from HubSpot by translating it\n * into a RateLimitException carrying Retry-After.\n *\n * Wrap any outbound HubSpot call (SDK or raw HTTP) like:\n *\n * $this->executeRequest(fn () => $this->getNewInstance()->crm()->...);\n *\n * @template T\n * @param callable(): T $apiCall\n * @return T\n *\n * @throws RateLimitException\n */\n private function executeRequest(callable $apiCall)\n {\n if (! $this->rateLimiter->canMakeRequest($this->config)) {\n $retryAfter = $this->rateLimiter->requestAvailableIn($this->config);\n\n $this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n ]);\n\n throw new RateLimitException(\n 'Hubspot rate limit reached for configuration ' . $this->config->getId(),\n $retryAfter,\n );\n }\n\n $this->rateLimiter->incrementRequestCount($this->config);\n\n try {\n return $apiCall();\n } catch (Throwable $e) {\n if ($this->isHubspotRateLimit($e)) {\n $retryAfter = $this->parseRetryAfter($e);\n\n $this->log->warning('[Hubspot] Received 429 from API', [\n 'team_id' => $this->config->team_id,\n 'config_id' => $this->config->getId(),\n 'retry_after' => $retryAfter,\n 'reason' => $e->getMessage(),\n ]);\n\n throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);\n }\n\n throw $e;\n }\n }\n\n private function isHubspotRateLimit(Throwable $e): bool\n {\n return method_exists($e, 'getCode') && (int) $e->getCode() === 429;\n }\n\n private function parseRetryAfter(Throwable $e): int\n {\n if (method_exists($e, 'getResponseHeaders')) {\n $headers = $e->getResponseHeaders() ?: [];\n $value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;\n if (is_array($value)) {\n $value = $value[0] ?? null;\n }\n if (is_numeric($value)) {\n return (int) $value;\n }\n }\n\n return 10;\n }\n\n public function getMinimumApiVersion(): string\n {\n return self::MIN_API_VERSION;\n }\n\n public function getInstance(): Factory\n {\n return new Factory([\n 'key' => $this->accessToken,\n 'oauth2' => true,\n 'base_url' => $this->baseUrl,\n ]);\n }\n\n public function getNewInstance(): Discovery\n {\n return \\HubSpot\\Factory::createWithAccessToken($this->accessToken);\n }\n\n /**\n * Secondly and daily limits for Hubspot API\n *\n * Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)\n * Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds\n * Daily: 250,000 | 500,000 | 1,000,000\n *\n * Official documentation states: The search endpoints are rate limited to five requests per second.\n * Since with 5 RPS were still hitting secondly rate limits we lowered it to 4\n */\n public function getPaginatedData(array $payload, string $type, int $offset = 0): array\n {\n $total = 0;\n $lastId = null;\n $rows = [];\n foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {\n $rows[] = $row;\n }\n\n return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];\n }\n\n /**\n * @throws HubspotException\n * @throws SocialAccountTokenInvalidException\n * @throws BadRequest\n */\n public function getPaginatedDataGenerator(\n array $payload,\n string $type,\n int $offset = 0,\n int &$total = 0,\n ?string &$lastRecordId = null\n ): \\Generator {\n return $this->paginationService->getPaginatedDataGenerator(\n $this,\n $payload,\n $type,\n $offset,\n $total,\n $lastRecordId\n );\n }\n\n /**\n * @throws DealApiException\n * @throws CrmException\n */\n public function getOpportunityById(string $crmId, array $fields): array\n {\n try {\n $deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n 'companies,contacts'\n ));\n } catch (DealApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $deal instanceof DealWithAssociations) {\n throw new CrmException('Deal not found');\n }\n\n return [\n 'id' => $deal->getId(),\n 'properties' => $deal->getProperties(),\n 'associations' => $deal->getAssociations(),\n ];\n }\n\n /**\n * Generic batch read method for HubSpot objects\n *\n * @param string $objectType The object type ('deals', 'companies', 'contacts')\n * @param array<string> $crmIds Array of HubSpot object IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with object data\n */\n private function batchReadObjects(string $objectType, array $crmIds, array $fields): array\n {\n if (empty($crmIds)) {\n return [];\n }\n\n $this->validateBatchSize($objectType, $crmIds);\n $this->ensureValidToken();\n\n try {\n $batchConfig = $this->createBatchConfiguration($objectType);\n $batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);\n $response = $batchConfig['api']->read($batchReadRequest);\n\n $this->validateApiResponse($response, $objectType);\n\n $results = $this->processApiResults($response);\n $this->logBatchResults($objectType, $crmIds, $results);\n\n return $results;\n } catch (\\Throwable $e) {\n $this->handleBatchError($e, $objectType, $crmIds);\n }\n }\n\n private function validateBatchSize(string $objectType, array $crmIds): void\n {\n if (count($crmIds) > 100) {\n throw new \\InvalidArgumentException(\"Batch size cannot exceed 100 {$objectType}\");\n }\n }\n\n private function createBatchConfiguration(string $objectType): array\n {\n $configurations = [\n 'deals' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Deals\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Deals\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->deals()->batchApi(),\n ],\n 'companies' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Companies\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Companies\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->companies()->batchApi(),\n ],\n 'contacts' => [\n 'batchReadRequest' => new \\HubSpot\\Client\\Crm\\Contacts\\Model\\BatchReadInputSimplePublicObjectId(),\n 'inputClass' => \\HubSpot\\Client\\Crm\\Contacts\\Model\\SimplePublicObjectId::class,\n 'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),\n ],\n ];\n\n if (! isset($configurations[$objectType])) {\n throw new \\InvalidArgumentException(\"Unsupported object type: {$objectType}\");\n }\n\n return $configurations[$objectType];\n }\n\n private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object\n {\n $batchReadRequest = $batchConfig['batchReadRequest'];\n $inputClass = $batchConfig['inputClass'];\n\n $inputs = array_map(function ($crmId) use ($inputClass) {\n $input = new $inputClass();\n $input->setId($crmId);\n\n return $input;\n }, $crmIds);\n\n $batchReadRequest->setInputs($inputs);\n $batchReadRequest->setProperties($fields);\n\n return $batchReadRequest;\n }\n\n private function validateApiResponse($response, string $objectType): void\n {\n if (! $response) {\n throw new CrmException(\"HubSpot API returned null response for {$objectType} batch read\");\n }\n }\n\n private function processApiResults($response): array\n {\n $results = [];\n $responseResults = $response->getResults();\n\n if ($responseResults) {\n foreach ($responseResults as $object) {\n if ($object && $object->getId()) {\n $results[$object->getId()] = [\n 'id' => $object->getId(),\n 'properties' => $object->getProperties() ?: [],\n ];\n }\n }\n }\n\n return $results;\n }\n\n private function logBatchResults(string $objectType, array $crmIds, array $results): void\n {\n $this->log->info(\"[HubSpot] Batch fetched {$objectType}\", [\n 'requested_count' => count($crmIds),\n 'returned_count' => count($results),\n 'crm_ids' => $crmIds,\n ]);\n }\n\n private function handleBatchError(\\Throwable $e, string $objectType, array $crmIds): void\n {\n $errorMessage = $e->getMessage() ?: 'Unknown error';\n $errorTrace = $e->getTraceAsString() ?: 'No trace available';\n\n $this->log->error(\"[HubSpot] Failed to batch fetch {$objectType}\", [\n 'crm_ids' => $crmIds,\n 'error' => $errorMessage,\n 'trace' => $errorTrace,\n ]);\n\n throw new CrmException(\"Failed to batch fetch {$objectType}: \" . $errorMessage);\n }\n\n /**\n * Batch read multiple opportunities by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot deal IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with opportunity data\n */\n public function getOpportunitiesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('deals', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple companies by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot company IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with company data\n */\n public function getCompaniesByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('companies', $crmIds, $fields);\n }\n\n /**\n * Batch read multiple contacts by their CRM IDs\n *\n * @param array<string> $crmIds Array of HubSpot contact IDs (max 100)\n * @param array<string> $fields Array of property names to fetch\n *\n * @return array<string, array> Array keyed by CRM ID with contact data\n */\n public function getContactsByIds(array $crmIds, array $fields): array\n {\n return $this->batchReadObjects('contacts', $crmIds, $fields);\n }\n\n /**\n * @throws CompanyApiException\n * @throws CrmException\n */\n public function getAccountById(string $crmId, array $fields): array\n {\n try {\n $company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(\n $crmId,\n implode(',', $fields),\n );\n } catch (CompanyApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch account', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $company instanceof CompaniesWithAssociations) {\n throw new CrmException('Account not found');\n }\n\n return [\n 'id' => $company->getId(),\n 'properties' => $company->getProperties(),\n ];\n }\n\n /**\n * @throws ContactApiException\n * @throws CrmException\n */\n public function getContactById(string $crmId, array $fields): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $crmId,\n implode(',', $fields)\n );\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'crm_id' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n if (! $contact instanceof ContactsWithAssociations) {\n throw new CrmException('Contact not found');\n }\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n }\n\n /**\n * This is email search request that Hubspot offers as GET (more generous quota)\n */\n public function getContactByEmail(string $email, array $fields = []): array\n {\n try {\n $contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(\n $email,\n implode(',', $fields),\n null,\n false,\n 'email'\n );\n\n return [\n 'id' => $contact->getId(),\n 'properties' => $contact->getProperties(),\n ];\n } catch (ContactApiException $e) {\n $this->log->info('[Hubspot] Failed to fetch contact', [\n 'email' => $email,\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n }\n\n /**\n * @throws CrmException\n */\n public function fetchProperty(string $objectType, string $propertyId): Property\n {\n $result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);\n\n if (! $result instanceof Property) {\n $this->log->error('[Hubspot] Failed to fetch property', [\n 'object_type' => $objectType,\n 'property_id' => $propertyId,\n 'reason' => $result->getMessage(),\n ]);\n\n throw new CrmException('Failed to fetch property');\n }\n\n return $result;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchPropertyOptions(string $objectType, string $propertyId): array\n {\n /** @var array<CrmFieldOption> */\n return $this->fetchProperty($objectType, $propertyId)->getOptions();\n }\n\n /**\n * @return array<array{id:string, label:string, deleted:bool}>\n */\n public function fetchCallDispositions(): array\n {\n /** @var Response $response */\n $response = $this->getInstance()->engagements()->getCallDispositions();\n\n /**\n * @var array<array{\n * id:string,\n * label:string,\n * deleted: bool\n * }>\n */\n return $response->toArray();\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityPipelineStages(): array\n {\n $stages = [];\n $apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');\n\n if ($apiResponse instanceof Error) {\n $this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $apiResponse->getMessage(),\n ]);\n\n return [];\n }\n\n foreach ($apiResponse->getResults() as $pipeline) {\n $pipelineStages = array_map(\n static function (PipelineStage $stage) {\n return [\n 'id' => $stage->getId(),\n 'label' => $stage->getLabel(),\n ];\n },\n $pipeline->getStages()\n );\n\n $stages = array_merge($stages, $pipelineStages);\n }\n\n return $stages;\n }\n\n public function fetchOpportunityPipelines(): array\n {\n $pipelines = [];\n\n try {\n $apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');\n } catch (\\Exception $e) {\n $this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [\n 'reason' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n $response = $apiResponse->toArray();\n\n foreach ($response['results'] as $pipeline) {\n $pipelines[] = [\n 'id' => $pipeline['id'],\n 'label' => $pipeline['label'],\n ];\n }\n\n return $pipelines;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchMeetingOutcomeFieldOptions(Field $field): array\n {\n return $field->getCrmProviderId() === 'meetingOutcome'\n ? $this->fetchMeetingOutcomeTypes()\n : $this->fetchCallActivityTypes();\n }\n\n public function fetchMeetingOutcomeTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/meeting/hs_meeting_outcome'\n );\n }\n\n public function fetchCallActivityTypes(): array\n {\n return $this->extractMeetingTypeOptions(\n 'https://api.hubapi.com/crm/v3/properties/call/hs_activity_type'\n );\n }\n\n private function extractMeetingTypeOptions(string $endpoint): array\n {\n /** @var Response $response */\n $response = $this->getInstance()\n ->getClient()\n ->request('GET', $endpoint);\n\n /**\n * @var array<array{\n * value: string,\n * label: string,\n * displayOrder: int\n * }> $optionData\n */\n $optionData = $response->toArray()['options'] ?? [];\n\n $options = [];\n foreach ($optionData as $item) {\n $options[] = [\n 'id' => $item['value'],\n 'value' => $item['value'],\n 'label' => $item['label'],\n 'display_order' => $item['displayOrder'],\n ];\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchDispositionFieldOptions(): array\n {\n $options = [];\n\n $dispositions = $this->fetchCallDispositions();\n\n foreach ($dispositions as $disposition) {\n if ($disposition['deleted'] !== false) {\n continue;\n }\n\n $option['value'] = $disposition['id'];\n $option['id'] = $disposition['id'];\n $option['label'] = $disposition['label'];\n\n $options[] = $option;\n }\n\n return $options;\n }\n\n /**\n * @return array<CrmFieldOption>\n */\n public function fetchOpportunityFieldOptions(Field $field): array\n {\n if ($field->isStageField()) {\n return $this->fetchOpportunityPipelineStages();\n }\n\n if ($field->isPipelineField()) {\n return $this->fetchOpportunityPipelines();\n }\n\n return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)\n {\n $endpoint = self::BASE_URL . $endpoint;\n\n if ($method === 'GET') {\n $response = $this->getInstance()->getClient()?->request(\n method: $method,\n endpoint: $endpoint,\n query_string: $queryString\n );\n } else {\n $response = $this->getInstance()->getClient()->request($method, $endpoint, [\n 'json' => ($payload),\n ]);\n }\n\n $max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // \"110\"\n $remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // \"109\"\n $interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // \"10000\"\n $body = json_decode((string) $response->getBody(), true);\n\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));\n \\Illuminate\\Support\\Facades\\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));\n\n return $response;\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function createMeeting(array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings';\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n /**\n * @throws BadRequest\n * @throws HubspotException\n */\n public function updateMeeting(string $meetingId, array $payload): Response\n {\n $endpoint = '/crm/v3/objects/meetings/' . $meetingId;\n\n return $this->makeRequest($endpoint, 'PATCH', $payload);\n }\n\n /**\n * @throws \\Exception\n */\n public function createNote(\n string $body,\n string $ownerId,\n int $timestamp,\n string $objectId,\n NoteObject $noteObject\n ): ?string {\n try {\n $noteInput = new SimplePublicObjectInput([\n 'properties' => [\n 'hs_note_body' => $body,\n 'hubspot_owner_id' => $ownerId,\n 'hs_timestamp' => $timestamp,\n ],\n ]);\n\n // Create note\n $note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);\n\n $this->getNewInstance()->crm()->objects()->associationsApi()->create(\n 'note',\n $note->getId(),\n $this->getNoteObject($noteObject),\n $objectId,\n $this->getNoteAssociationType($noteObject),\n );\n\n return $note->getId();\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to create note', [\n 'objectId' => $objectId,\n 'noteObject' => $noteObject->getObjectType(),\n 'reason' => $e->getMessage(),\n ]);\n\n \\Sentry::captureException($e);\n }\n\n return null;\n }\n\n public function updateEngagement(string $objectId, array $engagement, array $metadata): void\n {\n $this->getInstance()->engagements()->update($objectId, $engagement, $metadata);\n }\n\n public function getEngagementData(string $engagementId): array\n {\n $engagement = $this->getInstance()->engagements()->get($engagementId);\n\n return $engagement->toArray();\n }\n\n public function createEngagement(array $engagement, array $associations, array $metadata): Response\n {\n return $this->getInstance()\n ->engagements()\n ->create($engagement, $associations, $metadata);\n }\n\n public function isUnauthorizedException(\\Exception $e): bool\n {\n // Check for specific HubSpot API exception types first\n if ($e instanceof BadRequest) {\n // BadRequest can contain 401 status codes\n return $e->getCode() === 401;\n }\n\n // Check for HTTP client exceptions with status codes\n if ($e instanceof \\GuzzleHttp\\Exception\\RequestException && $e->hasResponse()) {\n $response = $e->getResponse();\n if ($response !== null) {\n return $response->getStatusCode() === 401;\n }\n }\n\n // Check for Guzzle HTTP exceptions\n if ($e instanceof \\GuzzleHttp\\Exception\\ClientException) {\n return $e->getCode() === 401;\n }\n\n // Fallback to string matching as last resort, but be more specific\n $message = strtolower($e->getMessage());\n\n return str_contains($message, '401 unauthorized') ||\n str_contains($message, 'http 401') ||\n str_contains($message, 'status code 401') ||\n (preg_match('/\\b401\\b/', $message) && str_contains($message, 'unauthorized'));\n }\n\n /**\n * Validates and refreshes the access token if needed before API requests.\n * This ensures long-running processes don't fail due to token expiration.\n *\n * @throws SocialAccountTokenInvalidException\n */\n public function ensureValidToken(): void\n {\n if ($this->oauthAccount === null) {\n return;\n }\n\n $newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);\n if ($newToken !== null) {\n $this->accessToken = $newToken;\n }\n }\n\n public function getConfig()\n {\n return $this->config;\n }\n\n // returns only active (archived=false)\n public function getOwners(): array\n {\n return $this->getNewInstance()->crm()->owners()->getAll();\n }\n\n /**\n * @param bool $archived\n *\n * @return array<Owner>|[]\n */\n public function getOwnersArchived(bool $archived = true): array\n {\n $endpoint = '/crm/v3/owners';\n $queryParams = [\n 'archived' => $archived ? 'true' : 'false',\n ];\n $queryString = http_build_query($queryParams);\n\n $owners = [];\n\n try {\n $response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);\n $responseData = $response?->toArray();\n\n foreach ($responseData['results'] as $result) {\n try {\n $owners[] = Owner::create($result);\n } catch (Throwable $e) {\n $this->log->error('[HubSpot] Failed to process owner data', [\n 'result' => $result,\n 'error' => $e->getMessage(),\n ]);\n\n continue;\n }\n }\n } catch (Throwable $e) {\n $this->log->error('HubSpot] Failed to fetch owners', [\n 'archived' => $archived,\n 'error' => $e->getMessage(),\n ]);\n\n return [];\n }\n\n return $owners;\n }\n\n public function getMeeting(string $engagementId): ObjectWithAssociations\n {\n return $this->getNewInstance()->crm()->objects()->basicApi()\n ->getById('meeting', $engagementId, null, 'contact,company,deal');\n }\n\n public function deleteEngagement(string $engagementId): void\n {\n $this->getInstance()->engagements()->delete((int) $engagementId);\n }\n\n public function getAssociationsData(array $ids, string $fromObject, string $toObject): array\n {\n $associationData = [];\n $idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);\n\n foreach ($idChunks as $idChunk) {\n try {\n $batchInput = new \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchInputPublicObjectId();\n $batchInput->setInputs(array_map(function ($id) {\n $publicObjectId = new \\HubSpot\\Client\\Crm\\Associations\\Model\\PublicObjectId();\n $publicObjectId->setId($id);\n\n return $publicObjectId;\n }, $idChunk));\n\n $associatedObjectsData = $this\n ->getNewInstance()\n ->crm()\n ->associations()\n ->batchApi()\n ->read($fromObject, $toObject, $batchInput);\n\n if ($associatedObjectsData instanceof \\HubSpot\\Client\\Crm\\Associations\\Model\\BatchResponsePublicAssociationMulti) {\n foreach ($associatedObjectsData->getResults() as $association) {\n $from = $association->getFrom()->getId();\n $toAssociations = $association->getTo();\n\n if (! empty($toAssociations)) {\n $associationData[$from] = array_map(function ($item) {\n return $item->getId();\n }, $toAssociations);\n }\n }\n }\n } catch (\\Exception $e) {\n $this->log->error('[Hubspot] Failed to fetch associations', [\n 'from_object' => $fromObject,\n 'to_object' => $toObject,\n 'reason' => $e->getMessage(),\n ]);\n }\n }\n\n return $associationData;\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteAssociationType(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'note_to_deal',\n NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it\n NoteObject::Account => 'note_to_company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n /**\n * @throws \\Exception\n */\n private function getNoteObject(NoteObject $noteObject): string\n {\n return match($noteObject) {\n NoteObject::Opportunity => 'deal',\n NoteObject::Lead, NoteObject::Contact => 'contact',\n NoteObject::Account => 'company',\n NoteObject::Call, NoteObject::Event => throw new \\Exception('Not supported'),\n };\n }\n\n public function addAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/create\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n\n public function removeAssociations(string $objectType, string $associationType, array $payload): Response\n {\n $endpoint = \"/crm/v4/associations/$objectType/$associationType/batch/archive\";\n\n return $this->makeRequest($endpoint, 'POST', $payload);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8926141032220118948
|
6378759383152203876
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"3cb37764-b6ec-4d4c-85b3-298d24411fec","trace_id":"1387c33f-5f58-4299-a3da-9c3ad7e240fe"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"997b55b4-ed52-4426-95b2-3d79f48278ae","trace_id":"9a0814b7-e0ae-4f6d-ad98-2979c51e16cb"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring start {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:13] local.NOTICE: Monitoring end {"correlation_id":"1f069a13-4342-40ed-bf79-1472c9c60637","trace_id":"1c2564a5-1d98-4061-819a-060f3143e672"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"92fadd4f-de00-4eae-8e71-26517f0e405c","trace_id":"034b1cf6-05b1-455d-9d58-e7286ec06e25"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
[2026-05-07 11:59:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.727,"memoryPeakAfterCommandInMB":99.727} {"correlation_id":"2bbdc0e4-afb2-4fa9-bbf7-b67fa2ca32cc","trace_id":"a7cdd06b-a602-49f9-a0f7-24736d4d641a"}
Sync Changes
Hide This Notification
Code changed:
Hide
2
68
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot;
use HubSpot\Client\Crm\Deals\ApiException as DealApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactApiException;
use HubSpot\Client\Crm\Companies\ApiException as CompanyApiException;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectWithAssociations as ContactsWithAssociations;
use HubSpot\Client\Crm\Companies\Model\SimplePublicObjectWithAssociations as CompaniesWithAssociations;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectWithAssociations as DealWithAssociations;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Objects\Model\SimplePublicObjectWithAssociations as ObjectWithAssociations;
use HubSpot\Client\Crm\Pipelines\Model\Error;
use HubSpot\Client\Crm\Pipelines\Model\PipelineStage;
use HubSpot\Client\Crm\Properties\Model\Property;
use HubSpot\Discovery\Discovery;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\RateLimitException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Models\Crm\Field;
use Jiminny\Services\Crm\BaseClient;
use Jiminny\Services\Crm\Hubspot\DTO\Response\Owner;
use Jiminny\Services\SocialAccountService;
use SevenShores\Hubspot\Exceptions\BadRequest;
use SevenShores\Hubspot\Exceptions\HubspotException;
use SevenShores\Hubspot\Factory;
use SevenShores\Hubspot\Http\Response;
use Jiminny\Services\Crm\Hubspot\Pagination\HubspotPaginationService;
use Throwable;
/**
* @phpstan-type CrmFieldOption array{id:string, label:string, value?:string}
*/
class Client extends BaseClient implements HubspotClientInterface
{
public const string MIN_API_VERSION = '2';
public const string BASE_URL = '[URL_WITH_CREDENTIALS] T
* @param callable(): T $apiCall
* @return T
*
* @throws RateLimitException
*/
private function executeRequest(callable $apiCall)
{
if (! $this->rateLimiter->canMakeRequest($this->config)) {
$retryAfter = $this->rateLimiter->requestAvailableIn($this->config);
$this->log->warning('[Hubspot] Rate limit exceeded, deferring request', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
]);
throw new RateLimitException(
'Hubspot rate limit reached for configuration ' . $this->config->getId(),
$retryAfter,
);
}
$this->rateLimiter->incrementRequestCount($this->config);
try {
return $apiCall();
} catch (Throwable $e) {
if ($this->isHubspotRateLimit($e)) {
$retryAfter = $this->parseRetryAfter($e);
$this->log->warning('[Hubspot] Received 429 from API', [
'team_id' => $this->config->team_id,
'config_id' => $this->config->getId(),
'retry_after' => $retryAfter,
'reason' => $e->getMessage(),
]);
throw new RateLimitException('Hubspot returned 429', $retryAfter, $e);
}
throw $e;
}
}
private function isHubspotRateLimit(Throwable $e): bool
{
return method_exists($e, 'getCode') && (int) $e->getCode() === 429;
}
private function parseRetryAfter(Throwable $e): int
{
if (method_exists($e, 'getResponseHeaders')) {
$headers = $e->getResponseHeaders() ?: [];
$value = $headers['Retry-After'] ?? $headers['retry-after'] ?? null;
if (is_array($value)) {
$value = $value[0] ?? null;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return 10;
}
public function getMinimumApiVersion(): string
{
return self::MIN_API_VERSION;
}
public function getInstance(): Factory
{
return new Factory([
'key' => $this->accessToken,
'oauth2' => true,
'base_url' => $this->baseUrl,
]);
}
public function getNewInstance(): Discovery
{
return \HubSpot\Factory::createWithAccessToken($this->accessToken);
}
/**
* Secondly and daily limits for Hubspot API
*
* Product Tier: Free & Starter | Professional & Enterprise | API add-on (any tier)
* Burst: 100/10 seconds | 150/10 seconds | 200/10 seconds
* Daily: 250,000 | 500,000 | 1,000,000
*
* Official documentation states: The search endpoints are rate limited to five requests per second.
* Since with 5 RPS were still hitting secondly rate limits we lowered it to 4
*/
public function getPaginatedData(array $payload, string $type, int $offset = 0): array
{
$total = 0;
$lastId = null;
$rows = [];
foreach ($this->getPaginatedDataGenerator($payload, $type, $offset, $total, $lastId) as $row) {
$rows[] = $row;
}
return ['results' => $rows, 'total' => $total, 'last_record' => $lastId];
}
/**
* @throws HubspotException
* @throws SocialAccountTokenInvalidException
* @throws BadRequest
*/
public function getPaginatedDataGenerator(
array $payload,
string $type,
int $offset = 0,
int &$total = 0,
?string &$lastRecordId = null
): \Generator {
return $this->paginationService->getPaginatedDataGenerator(
$this,
$payload,
$type,
$offset,
$total,
$lastRecordId
);
}
/**
* @throws DealApiException
* @throws CrmException
*/
public function getOpportunityById(string $crmId, array $fields): array
{
try {
$deal = $this->executeRequest(fn () => $this->getNewInstance()->crm()->deals()->basicApi()->getById(
$crmId,
implode(',', $fields),
'companies,contacts'
));
} catch (DealApiException $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $deal instanceof DealWithAssociations) {
throw new CrmException('Deal not found');
}
return [
'id' => $deal->getId(),
'properties' => $deal->getProperties(),
'associations' => $deal->getAssociations(),
];
}
/**
* Generic batch read method for HubSpot objects
*
* @param string $objectType The object type ('deals', 'companies', 'contacts')
* @param array<string> $crmIds Array of HubSpot object IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with object data
*/
private function batchReadObjects(string $objectType, array $crmIds, array $fields): array
{
if (empty($crmIds)) {
return [];
}
$this->validateBatchSize($objectType, $crmIds);
$this->ensureValidToken();
try {
$batchConfig = $this->createBatchConfiguration($objectType);
$batchReadRequest = $this->prepareBatchRequest($batchConfig, $crmIds, $fields);
$response = $batchConfig['api']->read($batchReadRequest);
$this->validateApiResponse($response, $objectType);
$results = $this->processApiResults($response);
$this->logBatchResults($objectType, $crmIds, $results);
return $results;
} catch (\Throwable $e) {
$this->handleBatchError($e, $objectType, $crmIds);
}
}
private function validateBatchSize(string $objectType, array $crmIds): void
{
if (count($crmIds) > 100) {
throw new \InvalidArgumentException("Batch size cannot exceed 100 {$objectType}");
}
}
private function createBatchConfiguration(string $objectType): array
{
$configurations = [
'deals' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Deals\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Deals\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->deals()->batchApi(),
],
'companies' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Companies\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Companies\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->companies()->batchApi(),
],
'contacts' => [
'batchReadRequest' => new \HubSpot\Client\Crm\Contacts\Model\BatchReadInputSimplePublicObjectId(),
'inputClass' => \HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectId::class,
'api' => $this->getNewInstance()->crm()->contacts()->batchApi(),
],
];
if (! isset($configurations[$objectType])) {
throw new \InvalidArgumentException("Unsupported object type: {$objectType}");
}
return $configurations[$objectType];
}
private function prepareBatchRequest(array $batchConfig, array $crmIds, array $fields): object
{
$batchReadRequest = $batchConfig['batchReadRequest'];
$inputClass = $batchConfig['inputClass'];
$inputs = array_map(function ($crmId) use ($inputClass) {
$input = new $inputClass();
$input->setId($crmId);
return $input;
}, $crmIds);
$batchReadRequest->setInputs($inputs);
$batchReadRequest->setProperties($fields);
return $batchReadRequest;
}
private function validateApiResponse($response, string $objectType): void
{
if (! $response) {
throw new CrmException("HubSpot API returned null response for {$objectType} batch read");
}
}
private function processApiResults($response): array
{
$results = [];
$responseResults = $response->getResults();
if ($responseResults) {
foreach ($responseResults as $object) {
if ($object && $object->getId()) {
$results[$object->getId()] = [
'id' => $object->getId(),
'properties' => $object->getProperties() ?: [],
];
}
}
}
return $results;
}
private function logBatchResults(string $objectType, array $crmIds, array $results): void
{
$this->log->info("[HubSpot] Batch fetched {$objectType}", [
'requested_count' => count($crmIds),
'returned_count' => count($results),
'crm_ids' => $crmIds,
]);
}
private function handleBatchError(\Throwable $e, string $objectType, array $crmIds): void
{
$errorMessage = $e->getMessage() ?: 'Unknown error';
$errorTrace = $e->getTraceAsString() ?: 'No trace available';
$this->log->error("[HubSpot] Failed to batch fetch {$objectType}", [
'crm_ids' => $crmIds,
'error' => $errorMessage,
'trace' => $errorTrace,
]);
throw new CrmException("Failed to batch fetch {$objectType}: " . $errorMessage);
}
/**
* Batch read multiple opportunities by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot deal IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with opportunity data
*/
public function getOpportunitiesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('deals', $crmIds, $fields);
}
/**
* Batch read multiple companies by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot company IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with company data
*/
public function getCompaniesByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('companies', $crmIds, $fields);
}
/**
* Batch read multiple contacts by their CRM IDs
*
* @param array<string> $crmIds Array of HubSpot contact IDs (max 100)
* @param array<string> $fields Array of property names to fetch
*
* @return array<string, array> Array keyed by CRM ID with contact data
*/
public function getContactsByIds(array $crmIds, array $fields): array
{
return $this->batchReadObjects('contacts', $crmIds, $fields);
}
/**
* @throws CompanyApiException
* @throws CrmException
*/
public function getAccountById(string $crmId, array $fields): array
{
try {
$company = $this->getNewInstance()->crm()->companies()->basicApi()->getById(
$crmId,
implode(',', $fields),
);
} catch (CompanyApiException $e) {
$this->log->info('[Hubspot] Failed to fetch account', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $company instanceof CompaniesWithAssociations) {
throw new CrmException('Account not found');
}
return [
'id' => $company->getId(),
'properties' => $company->getProperties(),
];
}
/**
* @throws ContactApiException
* @throws CrmException
*/
public function getContactById(string $crmId, array $fields): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$crmId,
implode(',', $fields)
);
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'crm_id' => $crmId,
'reason' => $e->getMessage(),
]);
throw $e;
}
if (! $contact instanceof ContactsWithAssociations) {
throw new CrmException('Contact not found');
}
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
}
/**
* This is email search request that Hubspot offers as GET (more generous quota)
*/
public function getContactByEmail(string $email, array $fields = []): array
{
try {
$contact = $this->getNewInstance()->crm()->contacts()->basicApi()->getById(
$email,
implode(',', $fields),
null,
false,
'email'
);
return [
'id' => $contact->getId(),
'properties' => $contact->getProperties(),
];
} catch (ContactApiException $e) {
$this->log->info('[Hubspot] Failed to fetch contact', [
'email' => $email,
'reason' => $e->getMessage(),
]);
return [];
}
}
/**
* @throws CrmException
*/
public function fetchProperty(string $objectType, string $propertyId): Property
{
$result = $this->getNewInstance()->crm()->properties()->coreApi()->getByName($objectType, $propertyId);
if (! $result instanceof Property) {
$this->log->error('[Hubspot] Failed to fetch property', [
'object_type' => $objectType,
'property_id' => $propertyId,
'reason' => $result->getMessage(),
]);
throw new CrmException('Failed to fetch property');
}
return $result;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchPropertyOptions(string $objectType, string $propertyId): array
{
/** @var array<CrmFieldOption> */
return $this->fetchProperty($objectType, $propertyId)->getOptions();
}
/**
* @return array<array{id:string, label:string, deleted:bool}>
*/
public function fetchCallDispositions(): array
{
/** @var Response $response */
$response = $this->getInstance()->engagements()->getCallDispositions();
/**
* @var array<array{
* id:string,
* label:string,
* deleted: bool
* }>
*/
return $response->toArray();
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityPipelineStages(): array
{
$stages = [];
$apiResponse = $this->getNewInstance()->crm()->pipelines()->pipelinesApi()->getAll('deals');
if ($apiResponse instanceof Error) {
$this->log->error('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $apiResponse->getMessage(),
]);
return [];
}
foreach ($apiResponse->getResults() as $pipeline) {
$pipelineStages = array_map(
static function (PipelineStage $stage) {
return [
'id' => $stage->getId(),
'label' => $stage->getLabel(),
];
},
$pipeline->getStages()
);
$stages = array_merge($stages, $pipelineStages);
}
return $stages;
}
public function fetchOpportunityPipelines(): array
{
$pipelines = [];
try {
$apiResponse = $this->makeRequest('/crm/v3/pipelines/deals');
} catch (\Exception $e) {
$this->log->info('[Hubspot] Failed to fetch opportunity pipelines', [
'reason' => $e->getMessage(),
]);
return [];
}
$response = $apiResponse->toArray();
foreach ($response['results'] as $pipeline) {
$pipelines[] = [
'id' => $pipeline['id'],
'label' => $pipeline['label'],
];
}
return $pipelines;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchMeetingOutcomeFieldOptions(Field $field): array
{
return $field->getCrmProviderId() === 'meetingOutcome'
? $this->fetchMeetingOutcomeTypes()
: $this->fetchCallActivityTypes();
}
public function fetchMeetingOutcomeTypes(): array
{
return $this->extractMeetingTypeOptions(
'[URL_WITH_CREDENTIALS] Response $response */
$response = $this->getInstance()
->getClient()
->request('GET', $endpoint);
/**
* @var array<array{
* value: string,
* label: string,
* displayOrder: int
* }> $optionData
*/
$optionData = $response->toArray()['options'] ?? [];
$options = [];
foreach ($optionData as $item) {
$options[] = [
'id' => $item['value'],
'value' => $item['value'],
'label' => $item['label'],
'display_order' => $item['displayOrder'],
];
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchDispositionFieldOptions(): array
{
$options = [];
$dispositions = $this->fetchCallDispositions();
foreach ($dispositions as $disposition) {
if ($disposition['deleted'] !== false) {
continue;
}
$option['value'] = $disposition['id'];
$option['id'] = $disposition['id'];
$option['label'] = $disposition['label'];
$options[] = $option;
}
return $options;
}
/**
* @return array<CrmFieldOption>
*/
public function fetchOpportunityFieldOptions(Field $field): array
{
if ($field->isStageField()) {
return $this->fetchOpportunityPipelineStages();
}
if ($field->isPipelineField()) {
return $this->fetchOpportunityPipelines();
}
return $this->fetchPropertyOptions('deals', $field->getCrmProviderId());
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function makeRequest(string $endpoint, $method = 'GET', $payload = [], ?string $queryString = null)
{
$endpoint = self::BASE_URL . $endpoint;
if ($method === 'GET') {
$response = $this->getInstance()->getClient()?->request(
method: $method,
endpoint: $endpoint,
query_string: $queryString
);
} else {
$response = $this->getInstance()->getClient()->request($method, $endpoint, [
'json' => ($payload),
]);
}
$max = $response->getHeaderLine('X-HubSpot-RateLimit-Max'); // "110"
$remaining = $response->getHeaderLine('X-HubSpot-RateLimit-Remaining'); // "109"
$interval = $response->getHeaderLine('X-HubSpot-RateLimit-Interval-Milliseconds'); // "10000"
$body = json_decode((string) $response->getBody(), true);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$max ' . PHP_EOL . print_r($max, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$remaining ' . PHP_EOL . print_r($remaining, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$interval ' . PHP_EOL . print_r($interval, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$body ' . PHP_EOL . print_r($body, true));
return $response;
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function createMeeting(array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings';
return $this->makeRequest($endpoint, 'POST', $payload);
}
/**
* @throws BadRequest
* @throws HubspotException
*/
public function updateMeeting(string $meetingId, array $payload): Response
{
$endpoint = '/crm/v3/objects/meetings/' . $meetingId;
return $this->makeRequest($endpoint, 'PATCH', $payload);
}
/**
* @throws \Exception
*/
public function createNote(
string $body,
string $ownerId,
int $timestamp,
string $objectId,
NoteObject $noteObject
): ?string {
try {
$noteInput = new SimplePublicObjectInput([
'properties' => [
'hs_note_body' => $body,
'hubspot_owner_id' => $ownerId,
'hs_timestamp' => $timestamp,
],
]);
// Create note
$note = $this->getNewInstance()->crm()->objects()->basicApi()->create('note', $noteInput);
$this->getNewInstance()->crm()->objects()->associationsApi()->create(
'note',
$note->getId(),
$this->getNoteObject($noteObject),
$objectId,
$this->getNoteAssociationType($noteObject),
);
return $note->getId();
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to create note', [
'objectId' => $objectId,
'noteObject' => $noteObject->getObjectType(),
'reason' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return null;
}
public function updateEngagement(string $objectId, array $engagement, array $metadata): void
{
$this->getInstance()->engagements()->update($objectId, $engagement, $metadata);
}
public function getEngagementData(string $engagementId): array
{
$engagement = $this->getInstance()->engagements()->get($engagementId);
return $engagement->toArray();
}
public function createEngagement(array $engagement, array $associations, array $metadata): Response
{
return $this->getInstance()
->engagements()
->create($engagement, $associations, $metadata);
}
public function isUnauthorizedException(\Exception $e): bool
{
// Check for specific HubSpot API exception types first
if ($e instanceof BadRequest) {
// BadRequest can contain 401 status codes
return $e->getCode() === 401;
}
// Check for HTTP client exceptions with status codes
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$response = $e->getResponse();
if ($response !== null) {
return $response->getStatusCode() === 401;
}
}
// Check for Guzzle HTTP exceptions
if ($e instanceof \GuzzleHttp\Exception\ClientException) {
return $e->getCode() === 401;
}
// Fallback to string matching as last resort, but be more specific
$message = strtolower($e->getMessage());
return str_contains($message, '401 unauthorized') ||
str_contains($message, 'http 401') ||
str_contains($message, 'status code 401') ||
(preg_match('/\b401\b/', $message) && str_contains($message, 'unauthorized'));
}
/**
* Validates and refreshes the access token if needed before API requests.
* This ensures long-running processes don't fail due to token expiration.
*
* @throws SocialAccountTokenInvalidException
*/
public function ensureValidToken(): void
{
if ($this->oauthAccount === null) {
return;
}
$newToken = $this->tokenManager->ensureValidToken($this->oauthAccount);
if ($newToken !== null) {
$this->accessToken = $newToken;
}
}
public function getConfig()
{
return $this->config;
}
// returns only active (archived=false)
public function getOwners(): array
{
return $this->getNewInstance()->crm()->owners()->getAll();
}
/**
* @param bool $archived
*
* @return array<Owner>|[]
*/
public function getOwnersArchived(bool $archived = true): array
{
$endpoint = '/crm/v3/owners';
$queryParams = [
'archived' => $archived ? 'true' : 'false',
];
$queryString = http_build_query($queryParams);
$owners = [];
try {
$response = $this->makeRequest(endpoint: $endpoint, queryString: $queryString);
$responseData = $response?->toArray();
foreach ($responseData['results'] as $result) {
try {
$owners[] = Owner::create($result);
} catch (Throwable $e) {
$this->log->error('[HubSpot] Failed to process owner data', [
'result' => $result,
'error' => $e->getMessage(),
]);
continue;
}
}
} catch (Throwable $e) {
$this->log->error('HubSpot] Failed to fetch owners', [
'archived' => $archived,
'error' => $e->getMessage(),
]);
return [];
}
return $owners;
}
public function getMeeting(string $engagementId): ObjectWithAssociations
{
return $this->getNewInstance()->crm()->objects()->basicApi()
->getById('meeting', $engagementId, null, 'contact,company,deal');
}
public function deleteEngagement(string $engagementId): void
{
$this->getInstance()->engagements()->delete((int) $engagementId);
}
public function getAssociationsData(array $ids, string $fromObject, string $toObject): array
{
$associationData = [];
$idChunks = array_chunk($ids, self::ASSOCIATIONS_BATCH_SIZE_LIMIT);
foreach ($idChunks as $idChunk) {
try {
$batchInput = new \HubSpot\Client\Crm\Associations\Model\BatchInputPublicObjectId();
$batchInput->setInputs(array_map(function ($id) {
$publicObjectId = new \HubSpot\Client\Crm\Associations\Model\PublicObjectId();
$publicObjectId->setId($id);
return $publicObjectId;
}, $idChunk));
$associatedObjectsData = $this
->getNewInstance()
->crm()
->associations()
->batchApi()
->read($fromObject, $toObject, $batchInput);
if ($associatedObjectsData instanceof \HubSpot\Client\Crm\Associations\Model\BatchResponsePublicAssociationMulti) {
foreach ($associatedObjectsData->getResults() as $association) {
$from = $association->getFrom()->getId();
$toAssociations = $association->getTo();
if (! empty($toAssociations)) {
$associationData[$from] = array_map(function ($item) {
return $item->getId();
}, $toAssociations);
}
}
}
} catch (\Exception $e) {
$this->log->error('[Hubspot] Failed to fetch associations', [
'from_object' => $fromObject,
'to_object' => $toObject,
'reason' => $e->getMessage(),
]);
}
}
return $associationData;
}
/**
* @throws \Exception
*/
private function getNoteAssociationType(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'note_to_deal',
NoteObject::Lead, NoteObject::Contact => 'note_to_contact', // or 'note_to_lead' if your portal supports it
NoteObject::Account => 'note_to_company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
/**
* @throws \Exception
*/
private function getNoteObject(NoteObject $noteObject): string
{
return match($noteObject) {
NoteObject::Opportunity => 'deal',
NoteObject::Lead, NoteObject::Contact => 'contact',
NoteObject::Account => 'company',
NoteObject::Call, NoteObject::Event => throw new \Exception('Not supported'),
};
}
public function addAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/create";
return $this->makeRequest($endpoint, 'POST', $payload);
}
public function removeAssociations(string $objectType, string $associationType, array $payload): Response
{
$endpoint = "/crm/v4/associations/$objectType/$associationType/batch/archive";
return $this->makeRequest($endpoint, 'POST', $payload);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
3083
|
NULL
|
NULL
|
NULL
|
|
7552
|
337
|
3
|
2026-05-08T09:04:27.351931+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231067351_m1.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.47569445,"top":0.033333335,"width":0.05138889,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
visual_change
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
7551
|
NULL
|
NULL
|
NULL
|
|
7553
|
338
|
2
|
2026-05-08T09:04:50.292193+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231090292_m2.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.49800533,"top":1.0,"width":0.024601065,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
idle
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
7554
|
337
|
4
|
2026-05-08T09:04:58.147972+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231098147_m1.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.47569445,"top":0.033333335,"width":0.05138889,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
idle
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
7555
|
338
|
3
|
2026-05-08T09:05:20.836514+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231120836_m2.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.49800533,"top":1.0,"width":0.024601065,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
idle
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
7553
|
NULL
|
NULL
|
NULL
|
|
7556
|
337
|
5
|
2026-05-08T09:05:28.654386+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231128654_m1.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.0,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.004166667,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.16458334,"top":0.05888889,"width":0.16458334,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.16875,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.32916668,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.33333334,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.49340278,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.49756944,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.6576389,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66180557,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.821875,"top":0.05888889,"width":0.16423611,"height":0.026666667},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.82604164,"top":0.06333333,"width":0.011111111,"height":0.017777778},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.95763886,"top":0.032222223,"width":0.03888889,"height":0.018888889},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.47569445,"top":0.033333335,"width":0.05138889,"height":0.017777778},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
idle
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
7554
|
NULL
|
NULL
|
NULL
|
|
7567
|
338
|
9
|
2026-05-08T09:06:38.829535+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778231198829_m2.jpg...
|
iTerm2
|
APP (-zsh)
|
True
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
[{"role":"AXTextArea","text [{"role":"AXTextArea","text":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","depth":4,"on_screen":true,"value":"Last login: Thu May 7 09:44:56 on ttys007\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\n\nPoetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status\nOn branch master\nYour branch is up to date with 'origin/master'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <file>...\" to discard changes in working directory)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: .env.local\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Console/Commands/JiminnyDebugCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: app/Services/PlaybackService.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: config/logging.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tmodified: resources/views/partials/crm/push-summary/html-assembly.blade.php\n\nUntracked files:\n (use \"git add <file>...\" to include in what will be committed)\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.nikilocal\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t.env.other\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tWEBHOOK_FILTERING_IMPLEMENTATION.md\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tids.txt\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tpublic/favicon.ico\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\traw_sql_query.sql\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ttests/Unit/Policies/CanAccessAiReportsTest.php\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nremote: Enumerating objects: 1482, done.\nremote: Counting objects: 100% (481/481), done.\nremote: Compressing objects: 100% (191/191), done.\nremote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)\nReceiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.\nResolving deltas: 100% (877/877), completed with 96 local objects.\nFrom github.com:jiminny/app\n 83b628967a..ad2ce76737 master -> origin/master\n 1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n 5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests\n b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n * [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost\n * [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc\n * [new branch] make-claude-great-again -> origin/make-claude-great-again\n * [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507\n * [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tresources/views/partials/crm/push-summary/html-assembly.blade.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nerror: Your local changes to the following files would be overwritten by merge:\n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\tapp/Jobs/Team/SyncToIntercom.php\nPlease commit your changes or stash them before you merge.\nAborting\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull\nUpdating 83b628967a..ad2ce76737\nFast-forward\n .cursor/rules/frontend-conventions.mdc | 23 ++\n .env.production-eu | 2 +-\n .env.staging | 2 +-\n Makefile | 10 +\n app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-\n app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-\n app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--\n app/Component/AskAnything/AskAnythingPromptService.php | 3 +\n app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-\n app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-\n app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-\n app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---\n app/Component/Twilio/TwilioRepository.php | 27 ++\n app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----\n app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--\n app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++\n app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----\n app/Console/Commands/Users/SyncToIntercom.php | 4 +-\n app/Console/Kernel.php | 3 +-\n app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -\n app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -\n app/Contracts/Repositories/TeamRepository.php | 3 +-\n app/Events/Activities/ActivityUpdated.php | 10 +-\n app/Events/Activities/Audio/RecordingEvent.php | 6 +-\n app/Events/Activities/Softphone/Ended.php | 8 +-\n app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-\n app/Events/Activities/Softphone/Started.php | 8 +-\n app/Http/Controllers/API/ActivityController.php | 17 +-\n app/Http/Controllers/API/SoftphoneController.php | 9 +-\n app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-\n app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-\n app/Http/Controllers/Auth/SocialController.php | 6 +-\n app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-\n app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-\n app/Http/Controllers/Kiosk/PartnersController.php | 46 +++\n app/Http/Controllers/Kiosk/SearchController.php | 8 +\n app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-\n app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-\n app/Http/Controllers/TeamSetupController.php | 4 +-\n app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-\n app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-\n app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +\n app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +\n app/Http/Transformers/ActivityTransformer.php | 4 +-\n app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-\n app/Http/Transformers/PartnerTransformer.php | 1 +\n app/Http/Transformers/StageTransformer.php | 6 +-\n app/Http/Transformers/UserTransformer.php | 11 +-\n app/Interactions/Settings/Teams/CreateTeam.php | 3 +\n app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-\n app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++\n app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++\n app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-\n app/Jobs/Crm/UpdateStage.php | 3 +\n app/Jobs/Team/SyncToIntercom.php | 7 +-\n app/Listeners/Teams/SyncIntercomCompany.php | 5 +-\n app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-\n app/Listeners/Users/SyncIntercom.php | 5 +-\n app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++\n app/Mail/Reports/ReportNotGenerated.php | 41 +++\n app/Models/Activity.php | 25 +-\n app/Models/Activity/Question.php | 14 +-\n app/Models/Activity/Search.php | 7 +\n app/Models/AskAnything/AskAnythingPrompt.php | 6 +\n app/Models/AutomatedReport.php | 10 +\n app/Models/CoachingFeedback.php | 44 ++-\n app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----\n app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----\n app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --\n app/Models/Partner.php | 13 +\n app/Models/Playlist/Activity.php | 14 +-\n app/Notifications/OwnerInvitedToTrial.php | 14 +-\n app/Policies/UserPolicy.php | 16 +-\n app/Queue/Worker/Worker.php | 3 +-\n app/Repositories/ActivityRepository.php | 13 +-\n app/Repositories/AutomatedReportsRepository.php | 42 ++-\n app/Repositories/TeamRepository.php | 21 +-\n app/Repositories/UserRepository.php | 2 +-\n app/Services/Activity/MeetingBotService.php | 8 +-\n app/Services/ActivityService.php | 111 ++-----\n app/Services/Crm/Hubspot/Service.php | 36 +-\n app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-\n app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-\n app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--\n app/Services/Kiosk/KioskService.php | 7 +-\n app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-\n app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++\n app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++\n app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++\n app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----\n composer.json | 1 -\n composer.lock | 95 +-----\n config/secure-headers.php | 5 +-\n database/mappings/mapping_activities.json | 16 +\n database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++\n database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++\n database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++\n database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++\n front-end/package.json | 5 +-\n front-end/src/__mocks__/jiminny.js | 4 +-\n front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +\n front-end/src/__mocks__/setup.js | 1 +\n front-end/src/apps/ai-reports-promo.js | 22 ++\n front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++\n front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++\n front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++\n .../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++\n front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-\n front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++\n front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++\n .../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++\n front-end/src/components/AiReports/constants.js | 7 +\n front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +\n front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-\n front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++\n front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +\n front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++\n front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-\n front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-\n front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-\n front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-\n front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++\n front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-\n front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++\n front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++\n front-end/src/main.js | 1 +\n front-end/src/store/modules/TeamInsights/util.js | 1 +\n front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++\n front-end/src/store/modules/platform/getters.js | 3 +\n front-end/src/utils/index.js | 11 +\n front-end/yarn.lock | 21 +-\n phpstan-baseline.neon | 60 ----\n public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes\n public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes\n public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes\n public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes\n public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes\n public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes\n public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes\n public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes\n resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++\n resources/views/emails/reports/report-not-generated.blade.php | 24 ++\n resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-\n routes/api.php | 6 +\n routes/web.php | 4 +\n tests/Feature/Policies/UserPolicyTest.php | 90 ++++-\n tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++\n tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++\n tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++\n tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++\n tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-\n tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++\n tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++\n tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++\n tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++\n tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++\n tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-\n tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++\n tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++\n tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-\n tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++\n tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++\n tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-\n tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +\n tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++\n tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-\n tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++\n tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++\n tests/Unit/Models/PartnerTest.php | 28 ++\n tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++\n tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-\n tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++\n tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--\n tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-\n tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++\n tests/Unit/Services/KioskServiceTest.php | 8 +\n tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-\n tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++\n tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++\n tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----\n 186 files changed, 8538 insertions(+), 1233 deletions(-)\n create mode 100644 app/Component/Twilio/TwilioRepository.php\n delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php\n create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php\n delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php\n create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php\n create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php\n create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php\n create mode 100644 app/Mail/Reports/ReportNotGenerated.php\n delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php\n create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php\n create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php\n create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php\n create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php\n create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php\n create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php\n create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php\n create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js\n create mode 100644 front-end/src/apps/ai-reports-promo.js\n create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js\n create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html\n create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js\n create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js\n create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js\n create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js\n create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/com/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf\n create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf\n create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf\n create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf\n create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf\n create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php\n create mode 100644 resources/views/emails/reports/report-not-generated.blade.php\n create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php\n create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php\n create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php\n create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php\n create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php\n create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php\n create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php\n create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php\n create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php\n create mode 100644 tests/Unit/Models/PartnerTest.php\n create mode 100644 tests/Unit/Services/ActivityServiceTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php\n create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull \nremote: Enumerating objects: 628, done.\nremote: Counting objects: 100% (331/331), done.\nremote: Compressing objects: 100% (63/63), done.\nremote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)\nReceiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.\nResolving deltas: 100% (391/391), completed with 57 local objects.\nFrom github.com:jiminny/app\n ad2ce76737..12295204cf master -> origin/master\n 14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3\n b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests\n f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null\n af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import\n * [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering\n 1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall\n * [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech\n + ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)\n * [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue\n * [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication\n * [new branch] desktop-app -> origin/desktop-app\n 4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again\n * [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas\nUpdating ad2ce76737..12295204cf\nFast-forward\n app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-\n app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------\n app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------\n tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------\n tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -\n tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -\n 11 files changed, 49 insertions(+), 90 deletions(-)\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric\nSwitched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix\ndocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff \nPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: 8.3.30\nRunning analysis on 7 cores with 10 files per process.\nParallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!\nLoaded config default from \".php-cs-fixer.dist.php\".\n 5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nFixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ ;xd\ndocker exec -it docker_lamp_1 bash -c \"mv /usr/local/etc/php/conf.d/xdebug.ini ~/xdebug.ini\"\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 supervisorctl restart all\njiminny-worker-processing-2:jiminny-worker-processing-2_00: stopped\njiminny-worker-processing-3:jiminny-worker-processing-3_00: stopped\njiminny-worker-processing-4:jiminny-worker-processing-4_00: stopped\njiminny-worker-processing-5:jiminny-worker-processing-5_00: stopped\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: stopped\nworker-analytics:worker-analytics_00: stopped\nworker-crm-update:worker-crm-update_00: stopped\nworker-download:worker-download_00: stopped\nworker-nudges:worker-nudges_00: stopped\nartisan-schedule:artisan-schedule_00: stopped\nworker-calendar:worker-calendar_00: stopped\nworker-crm-sync:worker-crm-sync_00: stopped\njiminny-worker-processing-1:jiminny-worker-processing-1_00: stopped\nworker:worker_00: stopped\nworker-emails:worker-emails_00: stopped\nworker-audio:worker-audio_00: stopped\nworker-conferences:worker-conferences_00: stopped\nworker-es-update:worker-es-update_00: stopped\nartisan-schedule:artisan-schedule_00: started\njiminny-worker-processing-1:jiminny-worker-processing-1_00: started\njiminny-worker-processing-2:jiminny-worker-processing-2_00: started\njiminny-worker-processing-3:jiminny-worker-processing-3_00: started\njiminny-worker-processing-4:jiminny-worker-processing-4_00: started\njiminny-worker-processing-5:jiminny-worker-processing-5_00: started\njiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00: started\nworker:worker_00: started\nworker-analytics:worker-analytics_00: started\nworker-audio:worker-audio_00: started\nworker-calendar:worker-calendar_00: started\nworker-conferences:worker-conferences_00: started\nworker-crm-sync:worker-crm-sync_00: started\nworker-crm-update:worker-crm-update_00: started\nworker-download:worker-download_00: started\nworker-emails:worker-emails_00: started\nworker-es-update:worker-es-update_00: started\nworker-nudges:worker-nudges_00: started\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\ndocker exec -it docker_lamp_1 php -v\nPHP 8.3.30 (cli) (built: Mar 16 2026 22:32:32) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.30, Copyright (c) Zend Technologies\n with Zend OPcache v8.3.30, Copyright (c), by Zend Technologies\n\nWhat's next:\n Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1\n Learn more at https://docs.docker.com/go/debug-cli/\nlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $","is_focused":true},{"role":"AXRadioButton","text":"DOCKER","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.27227393,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"DEV (docker)","depth":2,"bounds":{"left":0.34906915,"top":1.0,"width":0.0787899,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.35106382,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"APP (-zsh)","depth":2,"bounds":{"left":0.42785904,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.42985374,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.5064827,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.5084774,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"screenpipe\"","depth":2,"bounds":{"left":0.5851064,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.58710104,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"-zsh","depth":2,"bounds":{"left":0.66373,"top":1.0,"width":0.07862367,"height":-0.042298436},"on_screen":true,"role_description":"radio button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close Tab","depth":3,"bounds":{"left":0.66572475,"top":1.0,"width":0.005319149,"height":-0.04549086},"on_screen":true,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"⌥⌘1","depth":1,"bounds":{"left":0.7287234,"top":1.0,"width":0.01861702,"height":-0.023144484},"on_screen":true,"automation_id":"_NS:8","role_description":"text"},{"role":"AXStaticText","text":"APP (-zsh)","depth":1,"bounds":{"left":0.49800533,"top":1.0,"width":0.024601065,"height":-0.02394259},"on_screen":true,"role_description":"text"}]...
|
8925182857987417246
|
7003366906433808472
|
click
|
accessibility
|
NULL
|
Last login: Thu May 7 09:44:56 on ttys007
Poetry Last login: Thu May 7 09:44:56 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20773-fix-automated-reports-user-pilot-tracking) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .env.local
modified: app/Console/Commands/JiminnyDebugCommand.php
modified: app/Jobs/Team/SyncToIntercom.php
modified: app/Services/PlaybackService.php
modified: config/logging.php
modified: resources/views/partials/crm/push-summary/html-assembly.blade.php
Untracked files:
(use "git add <file>..." to include in what will be committed)
.env.nikilocal
.env.other
WEBHOOK_FILTERING_IMPLEMENTATION.md
app/Console/Commands/Crm/Hubspot/SimulateWebhooksCommand.php
app/Console/Commands/Reports/CreateMockAskJiminnyReportResultCommand.php
ids.txt
public/favicon.ico
raw_sql_query.sql
tests/Unit/Policies/CanAccessAiReportsTest.php
no changes added to commit (use "git add" and/or "git commit -a")
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 1482, done.
remote: Counting objects: 100% (481/481), done.
remote: Compressing objects: 100% (191/191), done.
remote: Total 1482 (delta 349), reused 305 (delta 289), pack-reused 1001 (from 4)
Receiving objects: 100% (1482/1482), 1017.97 KiB | 1.44 MiB/s, done.
Resolving deltas: 100% (877/877), completed with 96 local objects.
From github.com:jiminny/app
83b628967a..ad2ce76737 master -> origin/master
1ee8cbcb7b..14f54b5be2 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
5662c3b32f..b167b19973 JY-20289-api-tests -> origin/JY-20289-api-tests
b40408cfad..f23cfee7c3 JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
* [new branch] JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20662-remove-word-boost -> origin/JY-20662-remove-word-boost
* [new branch] JY-20742-mcp-poc -> origin/JY-20742-mcp-poc
* [new branch] make-claude-great-again -> origin/make-claude-great-again
* [new branch] secfix/composer-20260507 -> origin/secfix/composer-20260507
* [new branch] secfix/npm-20260507 -> origin/secfix/npm-20260507
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
resources/views/partials/crm/push-summary/html-assembly.blade.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
error: Your local changes to the following files would be overwritten by merge:
app/Jobs/Team/SyncToIntercom.php
Please commit your changes or stash them before you merge.
Aborting
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
Updating 83b628967a..ad2ce76737
Fast-forward
.cursor/rules/frontend-conventions.mdc | 23 ++
.env.production-eu | 2 +-
.env.staging | 2 +-
Makefile | 10 +
app/Component/ActivityAnalytics/Service/ActivityAnalyticsService.php | 6 +-
app/Component/AiAutomation/Repositories/AiTemplateFieldsRepository.php | 32 +-
app/Component/AiCallScoring/Repositories/AiScorecardRepository.php | 56 ++--
app/Component/AskAnything/AskAnythingPromptService.php | 3 +
app/Component/Transcription/Job/FinishTranscriptionJob.php | 37 ++-
app/Component/Transcription/TranscriptionProcessor/Gong/Gong.php | 18 +-
app/Component/Twilio/Conference/ConferenceManager/SoftPhoneManager.php | 4 +-
app/Component/Twilio/Service/SoftPhoneService.php | 124 ++++---
app/Component/Twilio/TwilioRepository.php | 27 ++
app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php | 59 ----
app/Console/Commands/Reports/AutomatedReportsCommand.php | 122 +++++--
app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php | 200 ++++++++++++
app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php | 60 ----
app/Console/Commands/Users/SyncToIntercom.php | 4 +-
app/Console/Kernel.php | 3 +-
app/Contracts/ES/Events/UpdateMultipleEntities.php | 4 -
app/Contracts/ES/Events/UpdateSingleEntity.php | 4 -
app/Contracts/Repositories/TeamRepository.php | 3 +-
app/Events/Activities/ActivityUpdated.php | 10 +-
app/Events/Activities/Audio/RecordingEvent.php | 6 +-
app/Events/Activities/Softphone/Ended.php | 8 +-
app/Events/Activities/Softphone/SoftphoneEvent.php | 24 +-
app/Events/Activities/Softphone/Started.php | 8 +-
app/Http/Controllers/API/ActivityController.php | 17 +-
app/Http/Controllers/API/SoftphoneController.php | 9 +-
app/Http/Controllers/API/UserAutomatedReports/UserAutomatedReportsController.php | 19 +-
app/Http/Controllers/API/V2/AskAnythingController.php | 2 +-
app/Http/Controllers/Auth/SocialController.php | 6 +-
app/Http/Controllers/Kiosk/AutomatedReportsController.php | 38 ++-
app/Http/Controllers/Kiosk/OrganizationsController.php | 8 +-
app/Http/Controllers/Kiosk/PartnersController.php | 46 +++
app/Http/Controllers/Kiosk/SearchController.php | 8 +
app/Http/Controllers/Kiosk/Teams/OnboardController.php | 24 +-
app/Http/Controllers/Settings/Teams/IntegrationController.php | 6 +-
app/Http/Controllers/TeamSetupController.php | 4 +-
app/Http/Controllers/Telephony/TextMessaging/MessageController.php | 12 +-
app/Http/Controllers/Telephony/TextMessaging/WebhookController.php | 18 +-
app/Http/Requests/Settings/Teams/CreateTeamRequest.php | 1 +
app/Http/Requests/Settings/Teams/EditTeamRequest.php | 1 +
app/Http/Transformers/ActivityTransformer.php | 4 +-
app/Http/Transformers/OnDemandActivitiesTransformer.php | 2 +-
app/Http/Transformers/PartnerTransformer.php | 1 +
app/Http/Transformers/StageTransformer.php | 6 +-
app/Http/Transformers/UserTransformer.php | 11 +-
app/Interactions/Settings/Teams/CreateTeam.php | 3 +
app/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJob.php | 80 ++++-
app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php | 119 +++++++
app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php | 89 +++++
app/Jobs/Crm/Hubspot/ImportBatchJobTrait.php | 12 +-
app/Jobs/Crm/UpdateStage.php | 3 +
app/Jobs/Team/SyncToIntercom.php | 7 +-
app/Listeners/Teams/SyncIntercomCompany.php | 5 +-
app/Listeners/Teams/UpdateSalesforceAccount.php | 8 +-
app/Listeners/Users/SyncIntercom.php | 5 +-
app/Mail/Reports/AskJiminnyReportExpiringMail.php | 40 +++
app/Mail/Reports/ReportNotGenerated.php | 41 +++
app/Models/Activity.php | 25 +-
app/Models/Activity/Question.php | 14 +-
app/Models/Activity/Search.php | 7 +
app/Models/AskAnything/AskAnythingPrompt.php | 6 +
app/Models/AutomatedReport.php | 10 +
app/Models/CoachingFeedback.php | 44 ++-
app/Models/ElasticSearch/ActivityElasticSearchTrait.php | 86 +----
app/Models/ElasticSearch/OpportunityElasticSearchTrait.php | 71 ----
app/Models/ElasticSearch/SharedDocumentDeleteTrait.php | 27 --
app/Models/Partner.php | 13 +
app/Models/Playlist/Activity.php | 14 +-
app/Notifications/OwnerInvitedToTrial.php | 14 +-
app/Policies/UserPolicy.php | 16 +-
app/Queue/Worker/Worker.php | 3 +-
app/Repositories/ActivityRepository.php | 13 +-
app/Repositories/AutomatedReportsRepository.php | 42 ++-
app/Repositories/TeamRepository.php | 21 +-
app/Repositories/UserRepository.php | 2 +-
app/Services/Activity/MeetingBotService.php | 8 +-
app/Services/ActivityService.php | 111 ++-----
app/Services/Crm/Hubspot/Service.php | 36 +-
app/Services/Crm/Hubspot/ServiceTraits/OpportunitySyncTrait.php | 2 +-
app/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityService.php | 5 +-
app/Services/Kiosk/AutomatedReports/AutomatedReportsService.php | 49 +--
app/Services/Kiosk/KioskService.php | 7 +-
app/Services/Webhook/Triggers/AiScorecardCompletedTrigger.php | 13 +-
app/UseCases/TeamInsights/ConversationRowMapper.php | 78 +++++
app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php | 68 ++++
app/UseCases/TeamInsights/StrictConsentColumnResolver.php | 45 +++
app/UseCases/TeamInsights/TeamConversationsExport.php | 154 ++++-----
composer.json | 1 -
composer.lock | 95 +-----
config/secure-headers.php | 5 +-
database/mappings/mapping_activities.json | 16 +
database/migrations/2026_04_14_000000_add_rockeed_partner.php | 51 +++
database/migrations/2026_04_22_000000_add_success_email_to_partners.php | 26 ++
database/migrations/2026_04_27_000000_add_label_to_partners.php | 28 ++
database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php | 79 +++++
front-end/package.json | 5 +-
front-end/src/__mocks__/jiminny.js | 4 +-
front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js | 9 +
front-end/src/__mocks__/setup.js | 1 +
front-end/src/apps/ai-reports-promo.js | 22 ++
front-end/src/components/AiReports/AiReportsPromo.vue | 22 ++
front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue | 190 +++++++++++
front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue | 111 +++++++
front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue | 103 ++++++
front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js | 98 ++++++
.../src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html | 283 ++++++++++++++++
front-end/src/components/AiReports/Manage/ManageAiReports.vue | 8 +-
front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue | 228 +++++++++++++
front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js | 71 ++++
.../src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html | 217 ++++++++++++
front-end/src/components/AiReports/constants.js | 7 +
front-end/src/components/Settings/Kiosk/OrganizationSearch/Organizations.vue | 1 +
front-end/src/components/Settings/Kiosk/__mocks__/Jiminny.js | 1 +
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/EditTeamModal.vue | 43 ++-
front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js | 203 ++++++++++++
front-end/src/components/Settings/Kiosk/shared/Navigation/Navigation.vue | 3 +
front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js | 67 ++++
front-end/src/components/TeamInsights/CoachingFrameworks/AICallScoring/aiCallScoringOverTime.ts | 4 +-
front-end/src/components/TeamInsights/CoachingFrameworks/UsersList.vue | 2 +-
front-end/src/components/layout/Sidebar/HelpMenu.vue | 25 +-
front-end/src/components/layout/Sidebar/Sidebar.vue | 27 +-
front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js | 94 ++++++
front-end/src/components/layout/Sidebar/__tests__/__snapshots__/Sidebar.spec.js.snap | 4 +-
front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js | 204 ++++++++++++
front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js | 49 +++
front-end/src/main.js | 1 +
front-end/src/store/modules/TeamInsights/util.js | 1 +
front-end/src/store/modules/platform/__tests__/getters.spec.js | 22 ++
front-end/src/store/modules/platform/getters.js | 3 +
front-end/src/utils/index.js | 11 +
front-end/yarn.lock | 21 +-
phpstan-baseline.neon | 60 ----
public/pdf/exec-reports/com/coaching-profiles.pdf | Bin 0 -> 1531178 bytes
public/pdf/exec-reports/com/exec-summary.pdf | Bin 0 -> 2237381 bytes
public/pdf/exec-reports/com/loss-report.pdf | Bin 0 -> 1955343 bytes
public/pdf/exec-reports/com/product-feedback.pdf | Bin 0 -> 2184417 bytes
public/pdf/exec-reports/eu/coaching-profiles.pdf | Bin 0 -> 1528704 bytes
public/pdf/exec-reports/eu/exec-summary.pdf | Bin 0 -> 2296741 bytes
public/pdf/exec-reports/eu/loss-report.pdf | Bin 0 -> 1955808 bytes
public/pdf/exec-reports/eu/product-feedback.pdf | Bin 0 -> 2184083 bytes
resources/views/emails/reports/ask-jiminny-report-expiring.blade.php | 22 ++
resources/views/emails/reports/report-not-generated.blade.php | 24 ++
resources/views/partials/crm/push-summary/html-assembly.blade.php | 2 +-
routes/api.php | 6 +
routes/web.php | 4 +
tests/Feature/Policies/UserPolicyTest.php | 90 ++++-
tests/Unit/Component/ActivityAnalytics/Service/ActivityAnalyticsServiceTest.php | 40 +++
tests/Unit/Component/AskAnything/AskAnythingPromptServiceTest.php | 26 ++
tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php | 276 ++++++++++++++++
tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php | 375 +++++++++++++++++++++
tests/Unit/Component/Twilio/Service/SoftPhoneServiceTest.php | 1014 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Unit/Console/Commands/Reports/AutomatedReportsCommandTest.php | 157 ++++++++-
tests/Unit/Events/Activities/Audio/RecordingEventTest.php | 72 ++++
tests/Unit/Events/Activities/Softphone/EndedTest.php | 86 +++++
tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php | 88 +++++
tests/Unit/Events/Activities/Softphone/StartedTest.php | 86 +++++
tests/Unit/Http/Controllers/Kiosk/AutomatedReportsControllerTest.php | 99 ++++++
tests/Unit/Http/Transformers/ActivityTransformerTest.php | 5 +-
tests/Unit/Http/Transformers/PartnerTransformerTest.php | 34 ++
tests/Unit/Interactions/Settings/Teams/CreateTeamTest.php | 49 +++
tests/Unit/Jobs/AutomatedReports/RequestGenerateAskJiminnyReportJobTest.php | 106 +++++-
tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php | 205 ++++++++++++
tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php | 188 +++++++++++
tests/Unit/Jobs/Crm/ImportOpportunityBatchTest.php | 2 +-
tests/Unit/Jobs/Team/SyncToIntercomTest.php | 6 +
tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php | 59 ++++
tests/Unit/Listeners/Teams/UpdateSalesforceAccountTest.php | 11 +-
tests/Unit/Listeners/Users/SyncIntercomTest.php | 59 ++++
tests/Unit/Mail/Reports/ReportNotGeneratedTest.php | 166 ++++++++++
tests/Unit/Models/PartnerTest.php | 28 ++
tests/Unit/Repositories/AutomatedReportsRepositoryTest.php | 68 ++++
tests/Unit/Services/Activity/MeetingBotServiceRequestRecordingToStopTest.php | 14 +-
tests/Unit/Services/ActivityServiceTest.php | 391 ++++++++++++++++++++++
tests/Unit/Services/Crm/Hubspot/ServiceResponseNormalizeTest.php | 68 ++--
tests/Unit/Services/Kiosk/AutomatedReports/AskJiminnyReportActivityServiceTest.php | 48 +--
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceActivitiesCountTest.php | 16 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceReportGenerationTest.php | 24 +-
tests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.php | 130 ++++++++
tests/Unit/Services/KioskServiceTest.php | 8 +
tests/Unit/Services/Webhook/Triggers/AiScorecardCompletedTriggerTest.php | 6 +-
tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php | 119 +++++++
tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php | 108 ++++++
tests/Unit/UseCases/TeamInsights/TeamConversationsExportTest.php | 342 ++++++++++++++-----
186 files changed, 8538 insertions(+), 1233 deletions(-)
create mode 100644 app/Component/Twilio/TwilioRepository.php
delete mode 100644 app/Console/Commands/CoachingFeedbacksUpdateEsActivities.php
create mode 100644 app/Console/Commands/RunAiCallScoringForUntypedActivitiesCommand.php
delete mode 100644 app/Console/Commands/UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php
create mode 100644 app/Http/Controllers/Kiosk/PartnersController.php
create mode 100644 app/Jobs/AutomatedReports/SendReportExpiringSoonMailJob.php
create mode 100644 app/Jobs/AutomatedReports/SendReportNotGeneratedMailJob.php
create mode 100644 app/Mail/Reports/AskJiminnyReportExpiringMail.php
create mode 100644 app/Mail/Reports/ReportNotGenerated.php
delete mode 100644 app/Models/ElasticSearch/SharedDocumentDeleteTrait.php
create mode 100644 app/UseCases/TeamInsights/ConversationRowMapper.php
create mode 100644 app/UseCases/TeamInsights/RecordingOutcomeTextResolver.php
create mode 100644 app/UseCases/TeamInsights/StrictConsentColumnResolver.php
create mode 100644 database/migrations/2026_04_14_000000_add_rockeed_partner.php
create mode 100644 database/migrations/2026_04_22_000000_add_success_email_to_partners.php
create mode 100644 database/migrations/2026_04_27_000000_add_label_to_partners.php
create mode 100644 database/migrations/2026_04_29_105053_move_ask_jiminny_reports_to_grow_tier.php
create mode 100644 front-end/src/__mocks__/kit/endpoints/automated-reports-promo.js
create mode 100644 front-end/src/apps/ai-reports-promo.js
create mode 100644 front-end/src/components/AiReports/AiReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/AutomatedReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/PromoCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/WhyItMattersCard.vue
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/AutomatedReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/AutomatedReportsPromo/__tests__/__snapshots__/automated-reports-promo.output.html
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/PanoramaReportsPromo.vue
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/PanoramaReportsPromo.spec.js
create mode 100644 front-end/src/components/AiReports/PanoramaReportsPromo/__tests__/__snapshots__/panorama-reports-promo.output.html
create mode 100644 front-end/src/components/Settings/Kiosk/modals/EditTeamModal/__tests__/EditTeamModal.spec.js
create mode 100644 front-end/src/components/Settings/Kiosk/shared/Navigation/__tests__/Navigation.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/HelpMenu.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/__tests__/useAiReportsSidebarButton.spec.js
create mode 100644 front-end/src/components/layout/Sidebar/useAiReportsSidebarButton.js
create mode 100644 front-end/src/store/modules/platform/__tests__/getters.spec.js
create mode 100644 public/pdf/exec-reports/com/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/com/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/com/loss-report.pdf
create mode 100644 public/pdf/exec-reports/com/product-feedback.pdf
create mode 100644 public/pdf/exec-reports/eu/coaching-profiles.pdf
create mode 100644 public/pdf/exec-reports/eu/exec-summary.pdf
create mode 100644 public/pdf/exec-reports/eu/loss-report.pdf
create mode 100644 public/pdf/exec-reports/eu/product-feedback.pdf
create mode 100644 resources/views/emails/reports/ask-jiminny-report-expiring.blade.php
create mode 100644 resources/views/emails/reports/report-not-generated.blade.php
create mode 100644 tests/Unit/Component/Transcription/Job/FinishTranscriptionJobTest.php
create mode 100644 tests/Unit/Component/Transcription/TranscriptionProcessor/Gong/GongTest.php
create mode 100644 tests/Unit/Events/Activities/Audio/RecordingEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/EndedTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/SoftphoneEventTest.php
create mode 100644 tests/Unit/Events/Activities/Softphone/StartedTest.php
create mode 100644 tests/Unit/Http/Transformers/PartnerTransformerTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportExpiringSoonMailJobTest.php
create mode 100644 tests/Unit/Jobs/AutomatedReports/SendReportNotGeneratedMailJobTest.php
create mode 100644 tests/Unit/Listeners/Teams/SyncIntercomCompanyTest.php
create mode 100644 tests/Unit/Listeners/Users/SyncIntercomTest.php
create mode 100644 tests/Unit/Mail/Reports/ReportNotGeneratedTest.php
create mode 100644 tests/Unit/Models/PartnerTest.php
create mode 100644 tests/Unit/Services/ActivityServiceTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/RecordingOutcomeTextResolverTest.php
create mode 100644 tests/Unit/UseCases/TeamInsights/StrictConsentColumnResolverTest.php
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pull
remote: Enumerating objects: 628, done.
remote: Counting objects: 100% (331/331), done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 628 (delta 280), reused 274 (delta 266), pack-reused 297 (from 1)
Receiving objects: 100% (628/628), 186.75 KiB | 1.23 MiB/s, done.
Resolving deltas: 100% (391/391), completed with 57 local objects.
From github.com:jiminny/app
ad2ce76737..12295204cf master -> origin/master
14f54b5be2..5e7646e5f9 JY-17836-participant-speeches-in-s3 -> origin/JY-17836-participant-speeches-in-s3
b167b19973..acba55cf38 JY-20289-api-tests -> origin/JY-20289-api-tests
f23cfee7c3..e5a3ec5dba JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null -> origin/JY-20352-sync-opportunities-without-a-local-owner-user-id-is-null
af59d60926..766efba1c5 JY-20395-fix-memory-issue-with-mail-import -> origin/JY-20395-fix-memory-issue-with-mail-import
* [new branch] JY-20493-smart-instant-nudge-pre-filtering -> origin/JY-20493-smart-instant-nudge-pre-filtering
1737b7c528..c57e71e763 JY-20606-desktop-app-recall -> origin/JY-20606-desktop-app-recall
* [new branch] JY-20671-anyvan-twilio-s3-recordings-ftech -> origin/JY-20671-anyvan-twilio-s3-recordings-ftech
+ ba181441c6...20a74137b0 JY-20742-mcp-poc -> origin/JY-20742-mcp-poc (forced update)
* [new branch] JY-20808-low-priority-indexing-queue -> origin/JY-20808-low-priority-indexing-queue
* [new branch] JY-20816-calendar-events-diplication -> origin/JY-20816-calendar-events-diplication
* [new branch] desktop-app -> origin/desktop-app
4c4c974e46..185442c26e make-claude-great-again -> origin/make-claude-great-again
* [new branch] mcp-tools-schemas -> origin/mcp-tools-schemas
Updating ad2ce76737..12295204cf
Fast-forward
app/Component/Encoding/Job/AnalyzeTrackChannelsJob.php | 2 +-
app/Component/Transcription/TranscriptionProcessor/AssemblyAI/Services/SubmitAudioFileService.php | 66 ++----------------------------------------------------------------
app/Console/Commands/Tracks/CleanupActivityTracksCommand.php | 24 +++++++++++++++++-------
tests/Unit/Console/Commands/Tracks/CleanupActivityTracksCommandTest.php | 40 +++++++++++++++++++++++++++++-----------
tests/Unit/fixtures/assembly_ai_transcript_response.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_channel_diarization.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_error.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_not_ready.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_language_post.json | 1 -
tests/Unit/fixtures/assembly_ai_transcript_response_without_utterances.json | 1 -
11 files changed, 49 insertions(+), 90 deletions(-)
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20818-move-AJ-reports-to-separated-datadog-metric
Switched to a new branch 'JY-20818-move-AJ-reports-to-separated-datadog-metric'
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $ csfix
docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --using-cache=no --diff
PHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.3.30
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from ".php-cs-fixer.dist.php".
5663/5663 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
Fixed 0 of 5663 files in 83.615 seconds, 67.00 MB memory used
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug docker_lamp_1
Learn more at [URL_WITH_CREDENTIALS] ~/jiminny/app (JY-20818-move-AJ-reports-to-separated-datadog-metric) $
DOCKER
Close Tab
DEV (docker)
Close Tab
APP (-zsh)
Close Tab
-zsh
Close Tab
screenpipe"
Close Tab
-zsh
Close Tab
⌥⌘1
APP (-zsh)...
|
NULL
|
NULL
|
NULL
|
NULL
|