portacode 0.3.20.dev0__tar.gz → 0.3.20.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/PKG-INFO +1 -1
  2. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/_version.py +2 -2
  3. portacode-0.3.20.dev2/portacode/connection/handlers/project_state/README.md +312 -0
  4. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/git_manager.py +20 -3
  5. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/handlers.py +21 -0
  6. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/manager.py +76 -10
  7. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/PKG-INFO +1 -1
  8. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/SOURCES.txt +1 -0
  9. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/.claude/agents/communication-manager.md +0 -0
  10. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/.claude/settings.local.json +0 -0
  11. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/.gitignore +0 -0
  12. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/.gitmodules +0 -0
  13. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/LICENSE +0 -0
  14. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/MANIFEST.in +0 -0
  15. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/Makefile +0 -0
  16. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/README.md +0 -0
  17. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/backup.sh +0 -0
  18. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/docker-compose.yaml +0 -0
  19. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/README.md +0 -0
  20. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/__init__.py +0 -0
  21. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/__main__.py +0 -0
  22. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/cli.py +0 -0
  23. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/README.md +0 -0
  24. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/__init__.py +0 -0
  25. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/client.py +0 -0
  26. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/README.md +0 -0
  27. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  28. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/__init__.py +0 -0
  29. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/base.py +0 -0
  30. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
  31. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/__init__.py +0 -0
  32. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  33. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/models.py +0 -0
  34. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state/utils.py +0 -0
  35. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/project_state_handlers.py +0 -0
  36. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/registry.py +0 -0
  37. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/session.py +0 -0
  38. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
  39. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/tab_factory.py +0 -0
  40. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
  41. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/multiplex.py +0 -0
  42. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/connection/terminal.py +0 -0
  43. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/data.py +0 -0
  44. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/keypair.py +0 -0
  45. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode/service.py +0 -0
  46. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/dependency_links.txt +0 -0
  47. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/entry_points.txt +0 -0
  48. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/requires.txt +0 -0
  49. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/portacode.egg-info/top_level.txt +0 -0
  50. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/pyproject.toml +0 -0
  51. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/restore.sh +0 -0
  52. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/run_tests.py +0 -0
  53. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/setup.cfg +0 -0
  54. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/setup.py +0 -0
  55. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test.sh +0 -0
  56. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/README.md +0 -0
  57. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/__init__.py +0 -0
  58. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_device_online.py +0 -0
  59. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_file_operations.py +0 -0
  60. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_login_flow.py +0 -0
  61. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_navigate_testing_folder.py +0 -0
  62. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_terminal_interaction.py +0 -0
  63. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/test_modules/test_terminal_start.py +0 -0
  64. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/.env.example +0 -0
  65. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/README.md +0 -0
  66. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/__init__.py +0 -0
  67. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/cli.py +0 -0
  68. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/__init__.py +0 -0
  69. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/base_test.py +0 -0
  70. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/cli_manager.py +0 -0
  71. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/hierarchical_runner.py +0 -0
  72. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/playwright_manager.py +0 -0
  73. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/runner.py +0 -0
  74. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/shared_cli_manager.py +0 -0
  75. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/core/test_discovery.py +0 -0
  76. {portacode-0.3.20.dev0 → portacode-0.3.20.dev2}/testing_framework/requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.20.dev0
3
+ Version: 0.3.20.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.20.dev0'
21
- __version_tuple__ = version_tuple = (0, 3, 20, 'dev0')
20
+ __version__ = version = '0.3.20.dev2'
21
+ __version_tuple__ = version_tuple = (0, 3, 20, 'dev2')
@@ -0,0 +1,312 @@
1
+ # Project State Management - Modular Architecture
2
+
3
+ This document explains the modular architecture of the project state management system, which was refactored from a single 3000+ line file into a well-organized, maintainable structure.
4
+
5
+ ## Overview
6
+
7
+ The project state management system handles:
8
+ - File system monitoring and change detection
9
+ - Git repository integration and operations
10
+ - Tab management (file tabs, diff tabs, etc.)
11
+ - Real-time project state synchronization
12
+ - Client session management
13
+
14
+ ## Architecture
15
+
16
+ The original monolithic `project_state_handlers.py` file has been broken down into the following modules:
17
+
18
+ ```
19
+ project_state/
20
+ ├── __init__.py # Public API exports
21
+ ├── models.py # Data structures and models
22
+ ├── git_manager.py # Git operations and repository management
23
+ ├── file_system_watcher.py # File system change monitoring
24
+ ├── manager.py # Central project state coordinator
25
+ ├── handlers.py # Request handlers for various operations
26
+ ├── utils.py # Utility functions and helpers
27
+ └── README.md # This documentation
28
+ ```
29
+
30
+ ## Module Details
31
+
32
+ ### 1. `models.py` - Data Structures
33
+
34
+ Contains all the dataclasses and models used throughout the system:
35
+
36
+ - **`ProjectState`**: Complete state of a project (files, git status, tabs, etc.)
37
+ - **`FileItem`**: Represents a file or directory with metadata
38
+ - **`TabInfo`**: Represents an editor tab with content and metadata
39
+ - **`MonitoredFolder`**: Represents a folder being monitored for changes
40
+ - **`GitFileChange`**: Represents a single file change in git
41
+ - **`GitDetailedStatus`**: Detailed git status with file hashes
42
+
43
+ **Key Features:**
44
+ - All models are dataclasses for easy serialization
45
+ - Comprehensive typing throughout
46
+ - Self-documenting field definitions
47
+
48
+ ### 2. `git_manager.py` - Git Operations
49
+
50
+ Handles all Git-related functionality:
51
+
52
+ **Core Responsibilities:**
53
+ - Repository detection and initialization
54
+ - File status tracking (staged, modified, untracked, etc.)
55
+ - Diff generation with syntax highlighting
56
+ - Content retrieval at different git references
57
+ - Git commands (stage, unstage, revert)
58
+
59
+ **Key Features:**
60
+ - Graceful fallback when GitPython is not available
61
+ - Performance optimizations for large files
62
+ - HTML diff generation with syntax highlighting
63
+ - Support for diff-match-patch algorithm
64
+ - Cross-platform path handling
65
+
66
+ **Performance Safeguards:**
67
+ - Timeouts for diff generation
68
+ - Size limits for large files
69
+ - Batch syntax highlighting for better performance
70
+ - Simplified diff view for very large files
71
+
72
+ ### 3. `file_system_watcher.py` - File System Monitoring
73
+
74
+ Monitors file system changes using the watchdog library:
75
+
76
+ **Core Responsibilities:**
77
+ - Cross-platform file system monitoring
78
+ - Event filtering and debouncing
79
+ - Git repository change detection
80
+ - Thread-safe async event handling
81
+
82
+ **Key Features:**
83
+ - Selective monitoring of relevant file changes
84
+ - Special handling for .git directory changes
85
+ - Debug tracing for troubleshooting
86
+ - Graceful fallback when watchdog is not available
87
+
88
+ **Event Filtering:**
89
+ - Skips temporary and debug files
90
+ - Focuses on meaningful file changes
91
+ - Monitors git-specific files for status updates
92
+
93
+ ### 4. `manager.py` - Central Coordinator
94
+
95
+ The main `ProjectStateManager` class that orchestrates all operations:
96
+
97
+ **Core Responsibilities:**
98
+ - Project state initialization and lifecycle
99
+ - Folder expansion/collapse logic
100
+ - Tab management (open, close, activate)
101
+ - File system change processing
102
+ - Client session management
103
+ - State synchronization and updates
104
+
105
+ **Key Features:**
106
+ - Singleton pattern for global state management
107
+ - Debounced file change processing
108
+ - Flattened item structure for performance
109
+ - Detailed debug logging and state tracking
110
+ - Client session isolation
111
+
112
+ **State Management:**
113
+ - Each client session has independent project state
114
+ - Monitored folders with expansion states
115
+ - Real-time synchronization with clients
116
+ - Orphaned state cleanup
117
+
118
+ ### 5. `handlers.py` - Request Handlers
119
+
120
+ AsyncHandler classes for different operations:
121
+
122
+ **Handler Classes:**
123
+ - `ProjectStateFolderExpandHandler` - Expand folders
124
+ - `ProjectStateFolderCollapseHandler` - Collapse folders
125
+ - `ProjectStateFileOpenHandler` - Open files in tabs
126
+ - `ProjectStateTabCloseHandler` - Close tabs
127
+ - `ProjectStateSetActiveTabHandler` - Set active tab
128
+ - `ProjectStateDiffOpenHandler` - Open diff tabs
129
+ - `ProjectStateGitStageHandler` - Stage files
130
+ - `ProjectStateGitUnstageHandler` - Unstage files
131
+ - `ProjectStateGitRevertHandler` - Revert files
132
+
133
+ **Key Features:**
134
+ - Consistent error handling and validation
135
+ - Server/client session mapping
136
+ - Automatic state updates after operations
137
+ - Comprehensive logging
138
+
139
+ ### 6. `utils.py` - Utility Functions
140
+
141
+ Shared utility functions:
142
+
143
+ - **`generate_tab_key()`**: Creates unique keys for different tab types
144
+ - Support for file tabs, diff tabs, untitled tabs
145
+ - Handles git reference parameters for diff tabs
146
+
147
+ ## Preserved Functionality
148
+
149
+ All functionality from the original file has been preserved:
150
+
151
+ ✅ **Complete Feature Parity**
152
+ - All classes, methods, and functions maintained
153
+ - Original behavior preserved exactly
154
+ - All logging statements preserved
155
+ - All documentation comments maintained
156
+
157
+ ✅ **Performance Optimizations**
158
+ - Large file handling safeguards
159
+ - Diff generation timeouts
160
+ - Syntax highlighting optimizations
161
+ - Memory-efficient processing
162
+
163
+ ✅ **Error Handling**
164
+ - Graceful fallbacks for missing dependencies
165
+ - Cross-platform compatibility
166
+ - Comprehensive exception handling
167
+ - Debug mode support
168
+
169
+ ✅ **Git Integration**
170
+ - Full GitPython integration
171
+ - Advanced diff capabilities
172
+ - Multi-reference comparisons
173
+ - HTML diff generation
174
+
175
+ ## Usage Examples
176
+
177
+ ### Basic Usage
178
+
179
+ ```python
180
+ # Import the main components
181
+ from project_state import (
182
+ get_or_create_project_state_manager,
183
+ ProjectState,
184
+ GitManager
185
+ )
186
+
187
+ # Get the global project state manager
188
+ manager = get_or_create_project_state_manager(context, control_channel)
189
+
190
+ # Initialize a project
191
+ project_state = await manager.initialize_project_state(
192
+ client_session_id="session123",
193
+ project_folder_path="/path/to/project"
194
+ )
195
+ ```
196
+
197
+ ### Using Individual Components
198
+
199
+ ```python
200
+ # Use GitManager independently
201
+ from project_state import GitManager
202
+
203
+ git_manager = GitManager("/path/to/project")
204
+ if git_manager.is_git_repo:
205
+ status = git_manager.get_detailed_status()
206
+ print(f"Branch: {git_manager.get_branch_name()}")
207
+ ```
208
+
209
+ ### Handler Integration
210
+
211
+ ```python
212
+ # Import handlers for request processing
213
+ from project_state import ProjectStateFolderExpandHandler
214
+
215
+ # Use in your handler registry
216
+ handler = ProjectStateFolderExpandHandler()
217
+ result = await handler.execute(message)
218
+ ```
219
+
220
+ ## Migration Guide
221
+
222
+ The refactoring maintains complete backward compatibility:
223
+
224
+ 1. **Existing imports continue to work** - The original module structure is preserved
225
+ 2. **No API changes** - All function signatures remain the same
226
+ 3. **Same behavior** - All functionality works exactly as before
227
+ 4. **Performance improvements** - Better organization enables easier optimization
228
+
229
+ ## Benefits of Modular Architecture
230
+
231
+ ### 1. **Maintainability**
232
+ - Single responsibility principle
233
+ - Easier to locate and modify specific functionality
234
+ - Reduced cognitive load when working on specific features
235
+
236
+ ### 2. **Testability**
237
+ - Individual modules can be tested in isolation
238
+ - Clear dependencies between components
239
+ - Easier mocking for unit tests
240
+
241
+ ### 3. **Reusability**
242
+ - Components can be used independently
243
+ - GitManager can be used outside project state context
244
+ - Models can be shared across different systems
245
+
246
+ ### 4. **Performance**
247
+ - Better import optimization
248
+ - Reduced memory footprint for unused components
249
+ - Clearer performance bottleneck identification
250
+
251
+ ### 5. **Documentation**
252
+ - Self-documenting module structure
253
+ - Clear separation of concerns
254
+ - Easier onboarding for new developers
255
+
256
+ ## Development Guidelines
257
+
258
+ ### Adding New Features
259
+
260
+ 1. **Identify the appropriate module** based on the feature's responsibility
261
+ 2. **Follow existing patterns** for consistency
262
+ 3. **Update the `__init__.py`** to export new public APIs
263
+ 4. **Add comprehensive logging** for debugging
264
+ 5. **Include performance safeguards** for resource-intensive operations
265
+
266
+ ### Modifying Existing Features
267
+
268
+ 1. **Maintain backward compatibility** unless breaking changes are explicitly approved
269
+ 2. **Preserve all logging statements** for debugging continuity
270
+ 3. **Update documentation** to reflect changes
271
+ 4. **Test across all affected modules** to ensure integration works
272
+
273
+ ### Best Practices
274
+
275
+ - Use type hints consistently
276
+ - Follow the established logging patterns
277
+ - Include docstrings for all public methods
278
+ - Handle errors gracefully with appropriate fallbacks
279
+ - Consider performance implications of changes
280
+
281
+ ## Troubleshooting
282
+
283
+ ### Common Issues
284
+
285
+ 1. **Import Errors**: Ensure you're importing from the correct module path
286
+ 2. **Missing Dependencies**: Check for optional dependencies (GitPython, watchdog, etc.)
287
+ 3. **Performance Issues**: Review file size limits and timeout settings
288
+ 4. **State Synchronization**: Check client session mapping and event handling
289
+
290
+ ### Debug Mode
291
+
292
+ Enable debug mode for detailed state tracking:
293
+
294
+ ```python
295
+ manager.set_debug_mode(True, "/path/to/debug.json")
296
+ ```
297
+
298
+ This creates a JSON file with complete project state information for analysis.
299
+
300
+ ## Future Enhancements
301
+
302
+ The modular architecture enables several future improvements:
303
+
304
+ 1. **Plugin System**: Easy addition of new file system watchers or git providers
305
+ 2. **Caching Layer**: Independent caching modules for performance
306
+ 3. **API Versioning**: Separate handler versions for backward compatibility
307
+ 4. **Microservice Architecture**: Components can be extracted to separate services
308
+ 5. **Enhanced Testing**: Module-specific test suites with better coverage
309
+
310
+ ## Conclusion
311
+
312
+ The modular architecture maintains all functionality while providing a solid foundation for future development. The separation of concerns makes the codebase more maintainable, testable, and extensible, while preserving the robust feature set that was built in the original implementation.
@@ -1075,9 +1075,26 @@ class GitManager:
1075
1075
  # Convert to relative path from repo root
1076
1076
  rel_path = os.path.relpath(file_path, self.repo.working_dir)
1077
1077
 
1078
- # Reset the file from HEAD (unstage)
1079
- self.repo.git.restore('--staged', rel_path)
1080
- logger.info("Successfully unstaged file: %s", rel_path)
1078
+ # Check if repository has any commits (HEAD exists)
1079
+ has_commits = False
1080
+ try:
1081
+ # Try to get HEAD commit to check if repository has commits
1082
+ self.repo.head.commit
1083
+ has_commits = True
1084
+ except Exception:
1085
+ logger.debug("Repository has no commits yet (no HEAD)")
1086
+ has_commits = False
1087
+
1088
+ if has_commits:
1089
+ # Repository has commits - use git restore --staged
1090
+ self.repo.git.restore('--staged', rel_path)
1091
+ logger.info("Successfully unstaged file using restore: %s", rel_path)
1092
+ else:
1093
+ # Repository has no commits - use git rm --cached
1094
+ # This handles the case where files are staged but no initial commit exists
1095
+ self.repo.git.rm('--cached', rel_path)
1096
+ logger.info("Successfully unstaged file using rm --cached (no HEAD): %s", rel_path)
1097
+
1081
1098
  return True
1082
1099
 
1083
1100
  except Exception as e:
@@ -395,6 +395,13 @@ class ProjectStateGitStageHandler(AsyncHandler):
395
395
  project_state = manager.projects[source_client_session]
396
396
  project_state.git_status_summary = git_manager.get_status_summary()
397
397
  project_state.git_detailed_status = git_manager.get_detailed_status()
398
+
399
+ # Force clear the last signature to ensure update is sent
400
+ # This is necessary because git operations should always trigger client updates
401
+ if hasattr(project_state, '_last_sent_signature'):
402
+ logger.info("Forcing git stage update by clearing signature cache")
403
+ project_state._last_sent_signature = None
404
+
398
405
  await manager._send_project_state_update(project_state, server_project_id)
399
406
 
400
407
  return {
@@ -444,6 +451,13 @@ class ProjectStateGitUnstageHandler(AsyncHandler):
444
451
  project_state = manager.projects[source_client_session]
445
452
  project_state.git_status_summary = git_manager.get_status_summary()
446
453
  project_state.git_detailed_status = git_manager.get_detailed_status()
454
+
455
+ # Force clear the last signature to ensure update is sent
456
+ # This is necessary because git operations should always trigger client updates
457
+ if hasattr(project_state, '_last_sent_signature'):
458
+ logger.info("Forcing git unstage update by clearing signature cache")
459
+ project_state._last_sent_signature = None
460
+
447
461
  await manager._send_project_state_update(project_state, server_project_id)
448
462
 
449
463
  return {
@@ -493,6 +507,13 @@ class ProjectStateGitRevertHandler(AsyncHandler):
493
507
  project_state = manager.projects[source_client_session]
494
508
  project_state.git_status_summary = git_manager.get_status_summary()
495
509
  project_state.git_detailed_status = git_manager.get_detailed_status()
510
+
511
+ # Force clear the last signature to ensure update is sent
512
+ # This is necessary because git operations should always trigger client updates
513
+ if hasattr(project_state, '_last_sent_signature'):
514
+ logger.info("Forcing git revert update by clearing signature cache")
515
+ project_state._last_sent_signature = None
516
+
496
517
  await manager._send_project_state_update(project_state, server_project_id)
497
518
 
498
519
  return {
@@ -413,8 +413,23 @@ class ProjectStateManager:
413
413
  # Update the monitored folder to expanded state
414
414
  monitored_folder = self._find_monitored_folder(project_state, folder_path)
415
415
  if not monitored_folder:
416
- logger.error("Monitored folder not found for path: %s", folder_path)
417
- return False
416
+ logger.warning("Monitored folder not found for path: %s. Attempting to add it...", folder_path)
417
+
418
+ # Check if the folder exists and is within the project
419
+ if not os.path.exists(folder_path) or not os.path.isdir(folder_path):
420
+ logger.error("Cannot expand folder - path does not exist or is not a directory: %s", folder_path)
421
+ return False
422
+
423
+ # Check if it's within the project root
424
+ if not folder_path.startswith(project_state.project_folder_path):
425
+ logger.error("Cannot expand folder - path is outside project root: %s", folder_path)
426
+ return False
427
+
428
+ # Add the folder to monitored_folders
429
+ logger.info("Adding missing folder to monitored_folders: %s", folder_path)
430
+ new_monitored = MonitoredFolder(folder_path=folder_path, is_expanded=False)
431
+ project_state.monitored_folders.append(new_monitored)
432
+ monitored_folder = new_monitored
418
433
 
419
434
  logger.info("Found monitored folder: %s, current is_expanded: %s", monitored_folder.folder_path, monitored_folder.is_expanded)
420
435
  monitored_folder.is_expanded = True
@@ -809,7 +824,11 @@ class ProjectStateManager:
809
824
  else:
810
825
  logger.info("🔍 [TRACE] ❌ No git manager found for session: %s", client_session_id)
811
826
 
812
- # Sync all dependent state (items, watchdog) - no automatic directory detection
827
+ # Detect and add new directories created in expanded folders to monitored_folders
828
+ logger.info("🔍 [TRACE] Detecting new directories in expanded folders...")
829
+ await self._detect_and_add_new_directories(project_state)
830
+
831
+ # Sync all dependent state (items, watchdog)
813
832
  logger.info("🔍 [TRACE] Syncing all state with monitored folders...")
814
833
  await self._sync_all_state_with_monitored_folders(project_state)
815
834
 
@@ -818,13 +837,46 @@ class ProjectStateManager:
818
837
  await self._send_project_state_update(project_state)
819
838
 
820
839
  async def _detect_and_add_new_directories(self, project_state: ProjectState):
821
- """Detect new directories in monitored folders and add them to monitoring."""
822
- # For each currently monitored folder, check if new subdirectories appeared
823
- monitored_folder_paths = [mf.folder_path for mf in project_state.monitored_folders]
840
+ """Detect new directories in expanded monitored folders and add them to monitoring."""
841
+ logger.info("🔍 [TRACE] _detect_and_add_new_directories called")
842
+
843
+ # Get currently expanded monitored folders
844
+ expanded_monitored_folders = [mf for mf in project_state.monitored_folders if mf.is_expanded]
845
+ logger.info("🔍 [TRACE] Found %d expanded monitored folders", len(expanded_monitored_folders))
824
846
 
825
- for folder_path in monitored_folder_paths:
826
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
827
- await self._add_subdirectories_to_monitored(project_state, folder_path)
847
+ existing_monitored_paths = {mf.folder_path for mf in project_state.monitored_folders}
848
+ new_folders_added = False
849
+
850
+ for monitored_folder in expanded_monitored_folders:
851
+ folder_path = monitored_folder.folder_path
852
+ logger.info("🔍 [TRACE] Scanning expanded folder for new directories: %s", folder_path)
853
+
854
+ if not os.path.exists(folder_path) or not os.path.isdir(folder_path):
855
+ logger.warning("🔍 [TRACE] Expanded folder no longer exists: %s", folder_path)
856
+ continue
857
+
858
+ try:
859
+ with os.scandir(folder_path) as entries:
860
+ for entry in entries:
861
+ if entry.is_dir() and entry.name != '.git':
862
+ if entry.path not in existing_monitored_paths:
863
+ logger.info("🔍 [TRACE] ✅ Found new directory to monitor: %s", entry.path)
864
+ # Add new directory as collapsed and loaded
865
+ new_monitored = MonitoredFolder(folder_path=entry.path, is_expanded=False)
866
+ project_state.monitored_folders.append(new_monitored)
867
+ existing_monitored_paths.add(entry.path)
868
+ new_folders_added = True
869
+ logger.info("🔍 [TRACE] Added new directory to monitored_folders: %s", entry.path)
870
+ else:
871
+ logger.debug("🔍 [TRACE] Directory already monitored: %s", entry.path)
872
+
873
+ except (OSError, PermissionError) as e:
874
+ logger.error("🔍 [TRACE] Error scanning expanded folder %s: %s", folder_path, e)
875
+
876
+ if new_folders_added:
877
+ logger.info("🔍 [TRACE] ✅ New directories were added to monitoring")
878
+ else:
879
+ logger.debug("🔍 [TRACE] No new directories found to add")
828
880
 
829
881
  async def _reload_visible_structures(self, project_state: ProjectState):
830
882
  """Reload all visible structures with flattened items."""
@@ -835,10 +887,24 @@ class ProjectStateManager:
835
887
  logger.info("🔍 [TRACE] _send_project_state_update called for session: %s", project_state.client_session_id)
836
888
 
837
889
  # Create state signature for change detection
890
+ # Include detailed git file information to ensure staging/unstaging triggers updates
891
+ git_detailed_signature = None
892
+ if project_state.git_detailed_status:
893
+ # Create a more detailed signature that captures individual file staging states
894
+ staged_files = tuple(sorted([(f.file_abs_path, f.change_type) for f in project_state.git_detailed_status.staged_changes]))
895
+ unstaged_files = tuple(sorted([(f.file_abs_path, f.change_type) for f in project_state.git_detailed_status.unstaged_changes]))
896
+ untracked_files = tuple(sorted([f.file_abs_path for f in project_state.git_detailed_status.untracked_files]))
897
+ git_detailed_signature = {
898
+ "head_commit_hash": project_state.git_detailed_status.head_commit_hash,
899
+ "staged_files": staged_files,
900
+ "unstaged_files": unstaged_files,
901
+ "untracked_files": untracked_files
902
+ }
903
+
838
904
  current_state_signature = {
839
905
  "git_branch": project_state.git_branch,
840
906
  "git_status_summary": project_state.git_status_summary,
841
- "git_detailed_status": str(project_state.git_detailed_status) if project_state.git_detailed_status else None,
907
+ "git_detailed_status": git_detailed_signature,
842
908
  "open_tabs": tuple((tab.tab_id, tab.tab_type, tab.title) for tab in project_state.open_tabs.values()),
843
909
  "active_tab": project_state.active_tab.tab_id if project_state.active_tab else None,
844
910
  "items_count": len(project_state.items),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.20.dev0
3
+ Version: 0.3.20.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -43,6 +43,7 @@ portacode/connection/handlers/session.py
43
43
  portacode/connection/handlers/system_handlers.py
44
44
  portacode/connection/handlers/tab_factory.py
45
45
  portacode/connection/handlers/terminal_handlers.py
46
+ portacode/connection/handlers/project_state/README.md
46
47
  portacode/connection/handlers/project_state/__init__.py
47
48
  portacode/connection/handlers/project_state/file_system_watcher.py
48
49
  portacode/connection/handlers/project_state/git_manager.py
File without changes
File without changes