rpr-cli 0.1.26__tar.gz → 0.1.27__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 (273) hide show
  1. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/PKG-INFO +1 -1
  2. rpr_cli-0.1.27/src/rpr/__init__.py +1 -0
  3. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/checks/instructions.py +1 -0
  4. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/add.py +156 -14
  5. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/ui.py +10 -2
  6. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/README.md +349 -0
  7. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/client/index.ts +1 -0
  8. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/client/types.ts +45 -0
  9. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/ChatComposer.spec.tsx +329 -0
  10. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/ChatComposer.tsx +358 -0
  11. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/__screenshots__/ChatComposer.spec.tsx/ChatComposer-renders-custom-action-buttons-beside-the-attachment-control-1.png +0 -0
  12. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/fileDrop.spec.ts +60 -0
  13. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/fileDrop.ts +128 -0
  14. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/index.ts +6 -0
  15. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/ChatComposerMentionTokens.tsx +139 -0
  16. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/ChatSuggestionsPopover.tsx +125 -0
  17. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/LocalFileSuggestionAction.tsx +100 -0
  18. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/index.ts +7 -0
  19. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/localFileSuggestions.spec.ts +281 -0
  20. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/localFileSuggestions.ts +1003 -0
  21. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/triggerDetection.spec.ts +56 -0
  22. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/triggerDetection.ts +58 -0
  23. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/types.ts +123 -0
  24. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/suggestions/useChatSuggestions.ts +339 -0
  25. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/types.ts +114 -0
  26. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/useChatComposer.ts +169 -0
  27. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/useChatStream.spec.tsx +104 -0
  28. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/composer/useChatStream.ts +212 -0
  29. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/index.ts +5 -0
  30. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/testing/createMockChatStreamClient.ts +91 -0
  31. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/testing/createMockSuggestionProvider.ts +99 -0
  32. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/testing/index.ts +2 -0
  33. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/ChatMessage.tsx +235 -0
  34. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/ChatMessageContent.spec.tsx +163 -0
  35. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/ChatMessageContent.tsx +670 -0
  36. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/ChatThread.spec.tsx +365 -0
  37. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/ChatThread.tsx +93 -0
  38. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/README.md +111 -0
  39. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/contentFormat.ts +30 -0
  40. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/index.ts +6 -0
  41. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/streaming.ts +174 -0
  42. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/thread/types.ts +135 -0
  43. rpr_cli-0.1.26/src/rpr/scaffolds/js_special_files/virtual-component.spec.md → rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/virtual-list/VirtualComponent.spec.tsx +39 -60
  44. rpr_cli-0.1.26/src/rpr/scaffolds/js_special_files/virtual-component.md → rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/virtual-list/VirtualComponent.tsx +12 -28
  45. rpr_cli-0.1.26/src/rpr/scaffolds/js_special_files/virtual-component-list.spec.md → rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/virtual-list/VirtualComponentList.spec.tsx +58 -85
  46. rpr_cli-0.1.26/src/rpr/scaffolds/js_special_files/virtual-component-list.md → rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/virtual-list/VirtualComponentList.tsx +32 -63
  47. rpr_cli-0.1.27/src/rpr/scaffolds/js_special_files/chat/virtual-list/index.ts +1 -0
  48. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/templates/registry.py +123 -51
  49. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_add.py +201 -3
  50. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_check.py +4 -0
  51. rpr_cli-0.1.26/src/rpr/__init__.py +0 -1
  52. rpr_cli-0.1.26/src/rpr/scaffolds/js_special_files/virtual-list-index.md +0 -9
  53. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/.github/agents/story.agent.md +0 -0
  54. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/.github/workflows/publish-pypi.yml +0 -0
  55. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/.gitignore +0 -0
  56. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/CLAUDE.md +0 -0
  57. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/GEMINI.md +0 -0
  58. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/LICENSE +0 -0
  59. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/README.md +0 -0
  60. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/all.instructions.md +0 -0
  61. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/api.instructions.md +0 -0
  62. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/domain.instructions.md +0 -0
  63. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/frontend.instructions.md +0 -0
  64. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/rust-engine.instructions.md +0 -0
  65. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/setup-guide.instructions.md +0 -0
  66. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/tooling-setup.instructions.md +0 -0
  67. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/instructions/tooling.instructions.md +0 -0
  68. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/README.md +0 -0
  69. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/api/main.py +0 -0
  70. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/domain/base-repo.md +0 -0
  71. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/domain/base_entity.md +0 -0
  72. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/api.service.ts +0 -0
  73. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/useStateNavigation.ts +0 -0
  74. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/virtual-list/VirtualComponent.spec.tsx +0 -0
  75. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/virtual-list/VirtualComponent.tsx +0 -0
  76. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/virtual-list/VirtualComponentList.spec.tsx +0 -0
  77. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/virtual-list/VirtualComponentList.tsx +0 -0
  78. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/Sample-dot-github/scaffolds/frontend/virtual-list/index.ts +0 -0
  79. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/changelogs/mui_v9.txt +0 -0
  80. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/code-map.md +0 -0
  81. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/nx_uv_setup.md +0 -0
  82. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/patch_output.py +0 -0
  83. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/plan-languageAwareTemplateAdd.prompt.md +0 -0
  84. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/pyproject.toml +0 -0
  85. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/Cargo.lock +0 -0
  86. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/Cargo.toml +0 -0
  87. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/pyproject.toml +0 -0
  88. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/rpr_parser.pyi +0 -0
  89. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/config/container.rs +0 -0
  90. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/config/mod.rs +0 -0
  91. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/config/settings.rs +0 -0
  92. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/graph.rs +0 -0
  93. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/lib.rs +0 -0
  94. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/models/dtos/mod.rs +0 -0
  95. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/models/entities/mod.rs +0 -0
  96. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/models/entities/users.rs +0 -0
  97. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/models/mod.rs +0 -0
  98. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/parse.rs +0 -0
  99. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/repos/base.rs +0 -0
  100. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/repos/mod.rs +0 -0
  101. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/repos/users.rs +0 -0
  102. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/resolve.rs +0 -0
  103. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/src/walker.rs +0 -0
  104. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/rpr-parser/uv.lock +0 -0
  105. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/special_files.md +0 -0
  106. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/__init__.py +0 -0
  107. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/approval.py +0 -0
  108. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/bootstrap.py +0 -0
  109. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/client.py +0 -0
  110. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/control.py +0 -0
  111. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/mock.py +0 -0
  112. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/runtime.py +0 -0
  113. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/session.py +0 -0
  114. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/tools/__init__.py +0 -0
  115. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/tools/base.py +0 -0
  116. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/tools/mutating.py +0 -0
  117. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/tools/readonly.py +0 -0
  118. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/agent/tools/registry.py +0 -0
  119. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/__init__.py +0 -0
  120. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/catalog.py +0 -0
  121. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/chat_service.py +0 -0
  122. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/checks.py +0 -0
  123. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/cli_adapter.py +0 -0
  124. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/completer.py +0 -0
  125. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/conversation_service.py +0 -0
  126. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/prompt_service.py +0 -0
  127. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/selector.py +0 -0
  128. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/application/shell.py +0 -0
  129. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/checks/__init__.py +0 -0
  130. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/checks/base.py +0 -0
  131. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/checks/packages.py +0 -0
  132. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/checks/workspace.py +0 -0
  133. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/cli.py +0 -0
  134. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/__init__.py +0 -0
  135. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/chat.py +0 -0
  136. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/check.py +0 -0
  137. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/__init__.py +0 -0
  138. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/api.py +0 -0
  139. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/domain.py +0 -0
  140. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/engine.py +0 -0
  141. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/generate/storybook.py +0 -0
  142. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/init.py +0 -0
  143. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/map.py +0 -0
  144. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/refresh.py +0 -0
  145. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/settings.py +0 -0
  146. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/commands/sync.py +0 -0
  147. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/context.py +0 -0
  148. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/dependencies.py +0 -0
  149. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/dependency_manifest_data.py +0 -0
  150. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/__init__.py +0 -0
  151. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/base.py +0 -0
  152. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/claude.py +0 -0
  153. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/copilot.py +0 -0
  154. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/cursor.py +0 -0
  155. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/gemini.py +0 -0
  156. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/generators/github.py +0 -0
  157. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/manifest_refresh.py +0 -0
  158. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/__init__.py +0 -0
  159. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/architecture.py +0 -0
  160. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/chains.py +0 -0
  161. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/classifier.py +0 -0
  162. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/coverage.py +0 -0
  163. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/dependencies.py +0 -0
  164. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/extractor.py +0 -0
  165. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/graph.py +0 -0
  166. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/output.py +0 -0
  167. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/responsibility.py +0 -0
  168. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/topology.py +0 -0
  169. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/map/walker.py +0 -0
  170. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/domain/base_entity.md +0 -0
  171. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/domain/base_repo.md +0 -0
  172. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/domain/container.md +0 -0
  173. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/domain/settings.md +0 -0
  174. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/all.instructions.md +0 -0
  175. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/api.instructions.md +0 -0
  176. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/domain.instructions.md +0 -0
  177. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/frontend.instructions.md +0 -0
  178. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/rust-engine.instructions.md +0 -0
  179. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/setup-guide.instructions.md +0 -0
  180. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/tooling-setup.instructions.md +0 -0
  181. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/instructions/tooling.instructions.md +0 -0
  182. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/js_special_files/fetch.service.md +0 -0
  183. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/js_special_files/sticky-navigation.md +0 -0
  184. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/js_special_files/sticky-navigation.spec.md +0 -0
  185. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/js_special_files/vitest-browser-config.md +0 -0
  186. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/domain_container.md +0 -0
  187. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/domain_settings.md +0 -0
  188. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/dto_util.md +0 -0
  189. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/encrypted_column.md +0 -0
  190. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/mapper_util.md +0 -0
  191. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/scaffolds/special_files/partial_update.md +0 -0
  192. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/templates/__init__.py +0 -0
  193. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/__init__.py +0 -0
  194. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/console.py +0 -0
  195. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/markdown.py +0 -0
  196. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/prompt_session.py +0 -0
  197. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/renderers.py +0 -0
  198. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/ui/theme.py +0 -0
  199. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/src/rpr/workspace.py +0 -0
  200. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/README.md +0 -0
  201. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/00-bootstrap-cli.md +0 -0
  202. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/01-rpr-init.md +0 -0
  203. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/02-rpr-generate-domain.md +0 -0
  204. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/03-rpr-generate-api.md +0 -0
  205. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/04-rpr-generate-ui.md +0 -0
  206. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/05-rpr-generate-engine.md +0 -0
  207. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/06-rpr-generate-storybook.md +0 -0
  208. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/07-rpr-sync-rules.md +0 -0
  209. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/08-rpr-add-template.md +0 -0
  210. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/09-rpr-check.md +0 -0
  211. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/10-rpr-domain-config.md +0 -0
  212. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/18-rpr-terminal-ui-foundation.md +0 -0
  213. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/19-rpr-markdown-rendering.md +0 -0
  214. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/20-rpr-chat-repl.md +0 -0
  215. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/21-rpr-agent-tool-loop.md +0 -0
  216. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/22-rpr-chat-tui.md +0 -0
  217. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/23-rpr-chat-unified-command-shell.md +0 -0
  218. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/24-rpr-async-chat-runtime-and-services.md +0 -0
  219. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/25-rpr-prompt-service-and-conversation-lifecycle.md +0 -0
  220. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/26-rpr-shared-command-catalog.md +0 -0
  221. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/27-rpr-slash-command-selection-panel.md +0 -0
  222. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/28-rpr-chat-cancellable-and-multiline-input.md +0 -0
  223. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/chat/29-rpr-file-context-selection.md +0 -0
  224. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/11-rpr-map-command.md +0 -0
  225. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/12-rpr-map-file-walker.md +0 -0
  226. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/13-rpr-map-rust-engine.md +0 -0
  227. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/13a-rpr-map-gitignore.md +0 -0
  228. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/14-rpr-map-graph-builder.md +0 -0
  229. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/15-rpr-map-analysis-bridge.md +0 -0
  230. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/16-rpr-map-layer-classifier.md +0 -0
  231. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/17-rpr-map-output.md +0 -0
  232. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/17a-rpr-map-directory-topology.md +0 -0
  233. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/17b-rpr-map-architecture-assessment.md +0 -0
  234. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/17c-rpr-map-representative-chains.md +0 -0
  235. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/completed/map/17d-rpr-map-multi-resolution-output.md +0 -0
  236. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/map/30-rpr-map-directory-view-and-cycle-detection.md +0 -0
  237. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/map/31-rpr-map-coupling-hotspots-and-verdict-reasoning.md +0 -0
  238. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/stories/map/32-rpr-map-dependencies-responsibilities-and-test-coverage.md +0 -0
  239. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/__init__.py +0 -0
  240. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_architecture.py +0 -0
  241. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_architecture_unwrapped.py +0 -0
  242. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_catalog.py +0 -0
  243. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_chat.py +0 -0
  244. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_chat_service.py +0 -0
  245. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_completer.py +0 -0
  246. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_context.py +0 -0
  247. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_generate_api.py +0 -0
  248. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_generate_domain.py +0 -0
  249. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_generate_engine.py +0 -0
  250. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_generate_storybook.py +0 -0
  251. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_generate_ui.py +0 -0
  252. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_init.py +0 -0
  253. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map.py +0 -0
  254. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_chains.py +0 -0
  255. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_classifier.py +0 -0
  256. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_coverage.py +0 -0
  257. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_dependencies.py +0 -0
  258. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_extractor.py +0 -0
  259. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_graph.py +0 -0
  260. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_output.py +0 -0
  261. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_responsibility.py +0 -0
  262. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_topology.py +0 -0
  263. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_map_walker.py +0 -0
  264. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_packaging.py +0 -0
  265. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_prompt_session.py +0 -0
  266. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_refresh.py +0 -0
  267. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_rpr_parser_stub.py +0 -0
  268. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_selector.py +0 -0
  269. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_settings.py +0 -0
  270. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_sync.py +0 -0
  271. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/tests/test_ui.py +0 -0
  272. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/update_output.py +0 -0
  273. {rpr_cli-0.1.26 → rpr_cli-0.1.27}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpr-cli
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: RPR CLI -- React Python Rust monorepo scaffolding orchestrator
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -0,0 +1 @@
1
+ __version__ = "0.1.27"
@@ -82,6 +82,7 @@ def check_instructions(
82
82
  ("fetch_service", "has_ui"),
83
83
  ("sticky_navigation", "has_ui"),
84
84
  ("virtual_list", "has_ui"),
85
+ ("chat", "has_ui"),
85
86
  ]
86
87
 
87
88
  for tid, prereq in template_ids:
@@ -62,6 +62,13 @@ def template(
62
62
  "--include-tests", help="Include unit-test companions when available."
63
63
  ),
64
64
  ] = False,
65
+ install: Annotated[
66
+ bool | None,
67
+ typer.Option(
68
+ "--install/--no-install",
69
+ help="Install npm dependencies after writing files (default depends on template).",
70
+ ),
71
+ ] = None,
65
72
  output_dir: Annotated[
66
73
  str | None,
67
74
  typer.Option(
@@ -115,24 +122,43 @@ def template(
115
122
  )
116
123
  raise typer.Exit(1)
117
124
 
118
- selected_files = list(entry.files)
119
- if include_tests:
120
- selected_files.extend(entry.test_files)
121
- selected_files.extend(entry.config_files)
122
-
123
- rendered_files = []
124
- for template_file in selected_files:
125
- source_file = source_dir / template_file.source_subdir / template_file.source_filename
126
- code = _render_template_file(source_file, template_file, variables)
127
- target_path = _resolve_target_path(ctx, template_file, variables, output_dir)
128
- rendered_files.append((template_file, target_path, code))
125
+ ordered_entries: list[tuple[TemplateEntry, bool]] = []
126
+ for required_id in entry.requires:
127
+ required_entry = TEMPLATES.get(required_id)
128
+ if required_entry is not None:
129
+ ordered_entries.append((required_entry, True))
130
+ ordered_entries.append((entry, False))
131
+
132
+ seen_targets: set[Path] = set()
133
+ rendered_files: list[tuple[TemplateFile, Path, str, bool]] = []
134
+ for current_entry, reuse_mode in ordered_entries:
135
+ files_for_entry = list(current_entry.files)
136
+ if include_tests:
137
+ files_for_entry.extend(current_entry.test_files)
138
+ files_for_entry.extend(current_entry.config_files)
139
+ for template_file in files_for_entry:
140
+ target_path = _resolve_target_path(
141
+ ctx, template_file, variables, output_dir
142
+ )
143
+ if target_path in seen_targets:
144
+ continue
145
+ seen_targets.add(target_path)
146
+ source_file = (
147
+ source_dir / template_file.source_subdir / template_file.source_filename
148
+ )
149
+ code = _render_template_file(source_file, template_file, variables)
150
+ rendered_files.append((template_file, target_path, code, reuse_mode))
129
151
 
130
152
  skipped_config_paths: list[Path] = []
153
+ reused_paths: list[Path] = []
131
154
  writable_files = []
132
- for template_file, target_path, code in rendered_files:
155
+ for template_file, target_path, code, reuse_mode in rendered_files:
133
156
  if dry_run or not target_path.exists() or force:
134
157
  writable_files.append((template_file, target_path, code))
135
158
  continue
159
+ if reuse_mode:
160
+ reused_paths.append(target_path)
161
+ continue
136
162
  if template_file.role == "config":
137
163
  console.print(
138
164
  f"[bold yellow]Warning:[/] Config already exists, leaving it unchanged: {target_path}"
@@ -150,11 +176,24 @@ def template(
150
176
  for _, target_path, code in writable_files:
151
177
  ctx.write_file(target_path, code)
152
178
 
179
+ _update_root_index(
180
+ ctx,
181
+ [target_path for _, target_path, _ in writable_files],
182
+ variables,
183
+ output_dir,
184
+ )
185
+
153
186
  _print_include_test_notes(entry, include_tests, skipped_config_paths)
154
187
 
188
+ _maybe_install_dependencies(ctx, ordered_entries, include_tests, install)
189
+
155
190
  console.print("\n[bold green]Done![/] Template files written:")
156
191
  for _, target_path, _ in writable_files:
157
192
  console.print(f" {target_path}")
193
+ if reused_paths:
194
+ console.print("\n[bold]Reused existing files:[/]")
195
+ for target_path in reused_paths:
196
+ console.print(f" {target_path}")
158
197
  if skipped_config_paths:
159
198
  console.print("\n[bold yellow]Skipped existing config files:[/]")
160
199
  for target_path in skipped_config_paths:
@@ -167,7 +206,9 @@ def _resolve_template_args(
167
206
  if lang_or_template_id in KNOWN_TEMPLATE_LANGS:
168
207
  return lang_or_template_id, template_id
169
208
  if template_id is not None:
170
- console.print(f"[bold red]Error:[/] Unknown template language: {lang_or_template_id}")
209
+ console.print(
210
+ f"[bold red]Error:[/] Unknown template language: {lang_or_template_id}"
211
+ )
171
212
  console.print("Supported languages: node, python")
172
213
  raise typer.Exit(1)
173
214
  return None, lang_or_template_id
@@ -233,6 +274,11 @@ def _render_template_file(
233
274
  console.print(f"[bold red]Error:[/] Source file not found: {source_file}")
234
275
  raise typer.Exit(1)
235
276
 
277
+ if template_file.raw:
278
+ code = source_file.read_text()
279
+ code = _interpolate_code(code, variables)
280
+ return _apply_raw_transforms(code, template_file, variables)
281
+
236
282
  markdown = source_file.read_text()
237
283
  code_blocks = _extract_code_blocks(markdown, template_file.language)
238
284
 
@@ -244,6 +290,32 @@ def _render_template_file(
244
290
  return _interpolate_code(code, variables)
245
291
 
246
292
 
293
+ def _apply_raw_transforms(
294
+ code: str, template_file: TemplateFile, variables: dict[str, str]
295
+ ) -> str:
296
+ """Rewrite project-specific paths/imports in raw chat scaffold files."""
297
+ subdir = template_file.source_subdir
298
+ filename = template_file.source_filename
299
+ name_kebab = variables.get("name_kebab", "")
300
+
301
+ # The chat thread reuses the sibling standalone virtual-list component.
302
+ if subdir.endswith("chat/thread") and filename == "ChatThread.tsx":
303
+ code = code.replace("from '../virtual-list'", "from '../../virtual-list'")
304
+
305
+ # The chat barrel must not re-export the reused virtual-list package.
306
+ if subdir.endswith("js_special_files/chat") and filename == "index.ts":
307
+ code = code.replace("export * from './virtual-list';\n", "")
308
+
309
+ # Documentation references to the originating project should be templated.
310
+ if filename.endswith(".md"):
311
+ code = code.replace("packages/huddle-ui", f"packages/node/{name_kebab}-ui")
312
+ code = code.replace("packages/sb/", f"packages/node/{name_kebab}-sb/")
313
+ code = code.replace("@huddle/ui", f"@{name_kebab}/ui")
314
+ code = code.replace("huddle-ui", f"{name_kebab}-ui")
315
+
316
+ return code
317
+
318
+
247
319
  def _interpolate_code(code: str, variables: dict[str, str]) -> str:
248
320
  code = interpolate(code, variables)
249
321
  name_val = variables.get("name", "")
@@ -255,6 +327,76 @@ def _interpolate_code(code: str, variables: dict[str, str]) -> str:
255
327
  return code
256
328
 
257
329
 
330
+ def _update_root_index(
331
+ ctx: RunContext,
332
+ written_targets: list[Path],
333
+ variables: dict[str, str],
334
+ output_dir: str | None,
335
+ ) -> None:
336
+ """Add component barrel exports to the UI package root index when present."""
337
+ if output_dir:
338
+ return
339
+
340
+ component_names: list[str] = []
341
+ for target in written_targets:
342
+ parts = target.parts
343
+ if "components" in parts:
344
+ index = parts.index("components")
345
+ if index + 1 < len(parts):
346
+ name = parts[index + 1]
347
+ if name not in component_names:
348
+ component_names.append(name)
349
+ if not component_names:
350
+ return
351
+
352
+ index_rel = interpolate("packages/node/{name_kebab}-ui/src/index.ts", variables)
353
+ index_path = ctx.project_dir / index_rel
354
+ if not index_path.exists():
355
+ return
356
+
357
+ existing = index_path.read_text()
358
+ additions = [
359
+ f'export * from "./components/{name}";'
360
+ for name in sorted(component_names)
361
+ if f'"./components/{name}"' not in existing
362
+ ]
363
+ if not additions:
364
+ return
365
+
366
+ updated = existing.rstrip("\n") + "\n" + "\n".join(additions) + "\n"
367
+ ctx.write_file(index_path, updated)
368
+
369
+
370
+ def _maybe_install_dependencies(
371
+ ctx: RunContext,
372
+ ordered_entries: list[tuple[TemplateEntry, bool]],
373
+ include_tests: bool,
374
+ install: bool | None,
375
+ ) -> None:
376
+ install_by_default = any(entry.install_by_default for entry, _ in ordered_entries)
377
+ should_install = install_by_default if install is None else install
378
+ if not should_install:
379
+ return
380
+
381
+ runtime: list[str] = []
382
+ dev: list[str] = []
383
+ for entry, _ in ordered_entries:
384
+ for dep in entry.dependencies:
385
+ if dep not in runtime:
386
+ runtime.append(dep)
387
+ if include_tests:
388
+ for dep in entry.dev_dependencies:
389
+ if dep not in dev:
390
+ dev.append(dep)
391
+
392
+ if runtime:
393
+ console.print("\n[bold]Installing runtime dependencies...[/]")
394
+ ctx.run_command(["npm", "i", *runtime], cwd=ctx.project_dir)
395
+ if dev:
396
+ console.print("\n[bold]Installing dev dependencies...[/]")
397
+ ctx.run_command(["npm", "i", "-D", *dev], cwd=ctx.project_dir)
398
+
399
+
258
400
  def _resolve_target_path(
259
401
  ctx: RunContext,
260
402
  template_file: TemplateFile,
@@ -295,4 +437,4 @@ def _find_source_dir(config: SyncConfig) -> Path | None:
295
437
  if common.exists():
296
438
  return common
297
439
 
298
- return None
440
+ return None
@@ -102,9 +102,17 @@ def ui(
102
102
  if include_library:
103
103
  console.print(" Optional templates: rpr add template node fetch_service")
104
104
  console.print(" rpr add template node sticky_navigation")
105
- console.print(" rpr add template node sticky_navigation --include-tests")
105
+ console.print(
106
+ " rpr add template node sticky_navigation --include-tests"
107
+ )
106
108
  console.print(" rpr add template node virtual_list")
107
- console.print(" rpr add template node virtual_list --include-tests")
109
+ console.print(
110
+ " rpr add template node virtual_list --include-tests"
111
+ )
112
+ console.print(" rpr add template node chat")
113
+ console.print(
114
+ " rpr add template node chat --include-tests"
115
+ )
108
116
 
109
117
 
110
118
  def _scaffold_ui_library(ctx: RunContext, ui_pkg_name: str) -> None:
@@ -0,0 +1,349 @@
1
+ # Portable Chat UI
2
+
3
+ The `chat` directory is intended to be portable UI infrastructure. A consumer should be able to copy this directory into another React/MUI app, install the listed dependencies, inject an app-specific stream client, and get the same thread, composer, file attachment, and streaming behavior.
4
+
5
+ ## What Is Included
6
+
7
+ - `thread` renders messages, content blocks, files, media, Markdown, HTML, math, code fences, avatars, and streaming chunks.
8
+ - `composer` renders the text/file input surface, native drag-and-drop handling, MUI file-icon attachments, generic trigger suggestions, send, and cancel controls.
9
+ - `client` defines the tiny injected streaming-client contract used by the composer hook.
10
+ - `testing` provides a deterministic mock streaming client for Storybook and unit tests.
11
+ - `virtual-list` keeps large message threads efficient.
12
+
13
+ The chat package should not import app-specific services, routes, auth state, stores, or generated API code directly.
14
+
15
+ ## Dependencies
16
+
17
+ Install the UI/runtime dependencies used by this package:
18
+
19
+ ```sh
20
+ npm install @mui/material@latest @mui/icons-material@latest @emotion/react@latest @emotion/styled@latest
21
+ npm install dompurify react-markdown remark-gfm remark-math rehype-katex katex prism-react-renderer
22
+ ```
23
+
24
+ The current project uses MUI 9. Keep MUI system styling in `sx` on MUI components because direct system props can leak to the DOM or fail generated declaration types in MUI 9.
25
+
26
+ ## API Service Boundary
27
+
28
+ The portable chat package consumes a `ChatStreamClient`. It does not own `fetch`, auth tokens, backend URLs, or generated API-service code.
29
+
30
+ Apps can generate a fetch service outside the chat folder with:
31
+
32
+ ```sh
33
+ rpr add template node fetch_service --output-dir ./
34
+ ```
35
+
36
+ Then wrap that generated service with the portable interface:
37
+
38
+ ```ts
39
+ import type { ChatStreamClient } from '@huddle/ui';
40
+
41
+ export const chatStreamClient: ChatStreamClient = {
42
+ async send(request) {
43
+ // POST request.text, request.attachments, request.threadId, and metadata.
44
+ // The backend converts the request to SSE.
45
+ // The adapter parses SSE events and yields ChatMessageChunkDto objects.
46
+ return parseSseResponseAsChatChunks(response, request.signal);
47
+ },
48
+ };
49
+ ```
50
+
51
+ Use JSON for text-only requests when that fits the backend. Use `FormData` when files are present. The generated `api.service.ts` shown in this repo JSON-stringifies `post` bodies, so a real file upload adapter should add a multipart or raw `fetch` path instead of forcing `File` objects through JSON.
52
+
53
+ ## Thread Ownership
54
+
55
+ `threadId` should normally be owned by the parent conversation controller, not by the composer. The composer hook can create and expose an active id when one is not provided, but selecting an old conversation, creating a new conversation, and managing multiple thread ids belongs above the portable chat package.
56
+
57
+ Recommended ownership:
58
+
59
+ - Parent app owns conversation selection and persistence.
60
+ - `useChatStream` accepts `threadId` and reports `activeThreadId`.
61
+ - The stream client sends the active id to the backend with each request.
62
+
63
+ ## Composer Lifecycle
64
+
65
+ The composer supports text, file attachments, suggestions, send, and cancel.
66
+
67
+ 1. User types a message or adds files.
68
+ 2. User presses Enter or clicks send.
69
+ 3. `useChatStream` appends an optimistic user message.
70
+ 4. The send button becomes a stable-size progress ring with a stop icon inside.
71
+ 5. The injected client sends the request with an `AbortSignal`.
72
+ 6. SSE chunks are normalized to `ChatMessageChunkDto` and merged with `mergeChatMessageChunk`.
73
+ 7. Clicking stop calls `cancel`, aborts the current request, and finalizes the visible partial response.
74
+
75
+ Use Shift+Enter for new lines. Enter sends when there is draft content.
76
+
77
+ The composer uses the full available width on mobile and a centered viewport width on larger screens: `width: { xs: '100%', sm: '80vw' }`, `maxWidth: '100%'`, `mx: 'auto'`, and `alignSelf: 'center'`. Override it at any time with `sx`:
78
+
79
+ ```tsx
80
+ <ChatComposer status={chat.status} onSend={chat.send} onCancel={chat.cancel} sx={{ width: { xs: '100%', sm: '72vw', lg: '900px' } }} />
81
+ ```
82
+
83
+ File attachments in the composer use MUI file icons rather than pills. Current categories include PDF, image, video, audio, spreadsheet, code, text, document, and generic file.
84
+
85
+ ## MUI Theming
86
+
87
+ Chat text is rendered through MUI primitives so app themes can control typography and color. Plain text uses `Typography`, Markdown text maps paragraphs/headings to `Typography`, Markdown links use MUI `Link`, and file labels use MUI text variants.
88
+
89
+ Use the MUI theme palette for semantic message states instead of hardcoded colors. Text content can opt into `tone: 'error'` or `tone: 'warning'`; message rows with `status: 'error'` render their text with `theme.palette.error.main`. System messages use `text.secondary`, and links use `primary.main`.
90
+
91
+ ## Suggestion System
92
+
93
+ `ChatComposer` supports generic trigger suggestions through injected providers. The portable chat package does not assume suggestions are users; each provider can return people, files, repos, tickets, symbols, or any app-specific entity.
94
+
95
+ ```tsx
96
+ import type { ChatSuggestionProvider } from '@huddle/ui';
97
+
98
+ const peopleProvider: ChatSuggestionProvider = {
99
+ id: 'people',
100
+ label: 'People',
101
+ trigger: '@',
102
+ async getSuggestions({ query, signal }) {
103
+ const response = await fetch(`/api/people?q=${encodeURIComponent(query)}`, { signal });
104
+ return response.json();
105
+ },
106
+ };
107
+
108
+ <ChatComposer status={chat.status} onSend={chat.send} onCancel={chat.cancel} suggestionsConfig={{ providers: [peopleProvider] }} />;
109
+ ```
110
+
111
+ Multiple providers can share the same trigger. For example, `@` can search both people and files, and the popover groups results by provider label.
112
+
113
+ Suggestion items separate visible text from backend context:
114
+
115
+ ```ts
116
+ {
117
+ id: 'user-avery',
118
+ displayValue: 'Avery Lee',
119
+ insertValue: '@Avery Lee',
120
+ description: 'Product lead',
121
+ payload: { type: 'user', userId: 'user-avery' },
122
+ }
123
+ ```
124
+
125
+ The composer inserts people and other text-backed selections directly into the text field and stores a hidden mention DTO in `draft.mentions`. `useChatStream` forwards those values to the injected `ChatStreamClient` as `request.mentions`, so the backend can receive stable ids and payloads without parsing display text.
126
+
127
+ Supported controls:
128
+
129
+ - typing a trigger opens a loading popover next to the composer input
130
+ - further typing calls providers again with the new query
131
+ - Up and Down move the highlighted result
132
+ - Enter or Tab selects the highlighted result while the popover is open
133
+ - Escape closes the popover
134
+ - clicking a result selects it
135
+ - clicking outside closes it
136
+ - whitespace closes normal single-token mention queries
137
+
138
+ Useful configuration knobs include `debounceMs`, `maxSuggestions`, `closeOnEmptyResults`, `appendSpaceOnSelect`, `popupSx`, and `renderSuggestion`.
139
+
140
+ File suggestions can opt into the same attachment path as drag/drop by providing real browser file data and a resolver:
141
+
142
+ ```tsx
143
+ <ChatComposer
144
+ onSend={chat.send}
145
+ suggestionsConfig={{
146
+ providers: [filesProvider],
147
+ getAttachmentFromSuggestion: (item) => {
148
+ const payload = item.payload as { file?: File } | undefined;
149
+ return payload?.file;
150
+ },
151
+ }}
152
+ />
153
+ ```
154
+
155
+ The resolver may return a `File`, `{ file }`, or `{ blob, name, mimeType, lastModified }`. When a suggestion resolves to a real file, the composer attaches the file and keeps the inserted text reference so the backend can see where the file was referenced in the message. Path-only suggestions, such as `{ path: 'docs/roadmap.md' }`, are metadata references; they are not equivalent to a dropped file unless the app resolves them to a real `File` or `Blob`. Selected suggestions still store mention metadata, so submitted user messages can render selected people inline and the backend can receive stable ids and ranges in `request.mentions`.
156
+
157
+ ### Built-In Local File Suggestions
158
+
159
+ The portable chat package includes a frontend-only local file suggestion provider. It indexes a user-selected folder, ranks files for `@` suggestions, and attaches selected suggestions as real browser `File` objects.
160
+
161
+ ```tsx
162
+ import { ChatComposer, LocalFileSuggestionAction, useLocalFileSuggestionProvider, type UseChatStreamResult } from '@huddle/ui';
163
+
164
+ function ChatInput({ chat }: { chat: UseChatStreamResult }) {
165
+ const fileSuggestions = useLocalFileSuggestionProvider({ maxIndexedFiles: 1500 });
166
+
167
+ return (
168
+ <ChatComposer
169
+ status={chat.status}
170
+ onSend={chat.send}
171
+ onCancel={chat.cancel}
172
+ actionButtons={<LocalFileSuggestionAction fileSuggestions={fileSuggestions} />}
173
+ suggestionsConfig={{
174
+ providers: [fileSuggestions.provider],
175
+ getAttachmentFromSuggestion: (item) => {
176
+ const payload = item.payload as { file?: File } | undefined;
177
+ return payload?.file;
178
+ },
179
+ }}
180
+ />
181
+ );
182
+ }
183
+ ```
184
+
185
+ The browser cannot silently index the user's home directory on first load. The local file provider stores the last granted directory handle in IndexedDB and reuses it when the browser still has read permission. If permission needs a user gesture, `LocalFileSuggestionAction` prompts from the composer toolbar. Common generated and system entries such as `node_modules`, `.git`, `.DS_Store`, `Thumbs.db`, and `System Volume Information` are skipped by default, and per-entry read errors are skipped so one protected file does not fail the whole index.
186
+
187
+ ### Composer Toolbar Actions
188
+
189
+ `ChatComposer` accepts `actionButtons?: React.ReactNode`. The prop name is plural because the node can be one icon button, an array, or a fragment containing multiple provider-specific actions.
190
+
191
+ ```tsx
192
+ <ChatComposer
193
+ onSend={chat.send}
194
+ actionButtons={
195
+ <>
196
+ <LocalFileSuggestionAction fileSuggestions={fileSuggestions} />
197
+ <RepoSuggestionAction provider={repoProvider} />
198
+ <TicketSuggestionAction provider={ticketProvider} />
199
+ </>
200
+ }
201
+ suggestionsConfig={{ providers: [fileSuggestions.provider, repoProvider, ticketProvider] }}
202
+ />
203
+ ```
204
+
205
+ Each action component should own its own icon button, tooltip, disabled state, dialogs, hidden inputs, and provider-specific setup logic. `ChatComposer` only decides where toolbar actions render; it does not know what each suggestion provider needs to configure itself.
206
+
207
+ Custom providers follow the same pattern: expose a provider for `suggestionsConfig.providers`, then optionally expose a toolbar action when the provider needs user setup.
208
+
209
+ ```tsx
210
+ const docsProvider: ChatSuggestionProvider = {
211
+ id: 'docs',
212
+ label: 'Docs',
213
+ trigger: '@',
214
+ async getSuggestions({ query, signal }) {
215
+ return searchDocs(query, { signal });
216
+ },
217
+ };
218
+
219
+ function DocsSuggestionAction() {
220
+ return (
221
+ <Tooltip title="Configure docs search">
222
+ <IconButton size="small" aria-label="Configure docs search">
223
+ <ManageSearchIcon fontSize="small" />
224
+ </IconButton>
225
+ </Tooltip>
226
+ );
227
+ }
228
+ ```
229
+
230
+ ## Latest Message Auto Scroll
231
+
232
+ `ChatThread` can scroll the latest message into view when messages change. This intentionally scrolls to the beginning of the newest message, which is better for assistant responses because the user lands at the top of the reply instead of the bottom of a growing stream.
233
+
234
+ ```tsx
235
+ <ChatThread messages={chat.messages} autoScrollToLatest />
236
+ ```
237
+
238
+ Defaults are `behavior: 'smooth'` and `block: 'start'`. Override them when needed:
239
+
240
+ ```tsx
241
+ <ChatThread messages={chat.messages} autoScrollToLatest autoScrollBehavior="auto" autoScrollBlock="nearest" />
242
+ ```
243
+
244
+ ## Native File Drag And Drop
245
+
246
+ The composer intentionally uses browser APIs directly instead of a drag/drop library.
247
+
248
+ The implementation uses:
249
+
250
+ - `dragenter`, `dragover`, `dragleave`, and `drop` for drop-zone state.
251
+ - `DataTransferItemList` for dropped items.
252
+ - `DataTransferItem.getAsFileSystemHandle()` when available for the modern File System Access API path.
253
+ - `DataTransferItem.getAsFile()` and `DataTransfer.files` as broad fallbacks.
254
+ - A hidden `<input type="file" multiple>` for keyboard access and browser fallback.
255
+
256
+ Attachment handling should dedupe files by `name`, `size`, and `lastModified`. Image object URLs must be revoked when removed, cleared, or unmounted. Directory drops are optional: support file handles when available, skip unsupported directories, and expose skipped items through a callback so the app can show a snackbar or inline note.
257
+
258
+ ## Hook Usage
259
+
260
+ Prefer the object-returning API because it can grow without tuple churn:
261
+
262
+ ```tsx
263
+ const chat = useChatStream({
264
+ client: chatStreamClient,
265
+ threadId,
266
+ userName: currentUser.name,
267
+ userAvatar: currentUser.avatar,
268
+ });
269
+
270
+ return (
271
+ <>
272
+ <ChatThread messages={chat.messages} />
273
+ <ChatComposer status={chat.status} onSend={chat.send} onCancel={chat.cancel} />
274
+ </>
275
+ );
276
+ ```
277
+
278
+ The earlier tuple idea, `const [send, cancel, threadId] = useChatStream(...)`, is intentionally not the canonical API. The feature needs status, messages, errors, active thread id, and setters, so the object API stays clearer.
279
+
280
+ ## Mock Client
281
+
282
+ Use `createMockChatStreamClient` in Storybook and tests. It returns deterministic variable-length Markdown, code, math, and attachment-aware responses without a backend.
283
+
284
+ ```tsx
285
+ const client = createMockChatStreamClient({ chunkDelayMs: 200, seed: 9 });
286
+ const chat = useChatStream({ client, createThreadId: () => 'storybook-thread' });
287
+ ```
288
+
289
+ Use `createMockSuggestionProvider` to demo or test trigger suggestions without a search backend:
290
+
291
+ ```tsx
292
+ const providers = [createMockSuggestionProvider({ id: 'people', label: 'People' })];
293
+
294
+ <ChatComposer onSend={chat.send} suggestionsConfig={{ providers }} />;
295
+ ```
296
+
297
+ ## Testing Expectations
298
+
299
+ Required coverage for the full chat experience:
300
+
301
+ - composer text input and Enter-to-send behavior
302
+ - native dropped files through `DataTransferItemList`
303
+ - picker fallback through hidden file input
304
+ - unsupported directory-drop fallback behavior
305
+ - attachment dedupe and object URL cleanup
306
+ - optimistic user message append
307
+ - streamed assistant chunk merge and finalization
308
+ - cancel/abort behavior
309
+ - trigger suggestion loading, keyboard navigation, selection, outside-click close, and hidden mention payloads
310
+ - multiple thread id isolation
311
+ - error handling and retry affordances
312
+ - render performance boundaries
313
+
314
+ Performance tests should verify:
315
+
316
+ - typing in the composer does not rerender old thread rows
317
+ - drag hover state only rerenders the composer/drop zone
318
+ - attachment changes do not rerender old messages
319
+ - sending a new message appends only the new user row
320
+ - streaming chunks rerender only the active assistant row
321
+
322
+ ## Storybook Expectations
323
+
324
+ Stories should include:
325
+
326
+ - text-only send
327
+ - native drag/drop attachments
328
+ - picker attachments
329
+ - slow cancellable stream
330
+ - `@` suggestions with one provider and multiple providers
331
+ - long Markdown/code/math responses
332
+ - error/retry state
333
+ - dark mode
334
+ - multiple thread ids
335
+
336
+ Current interactive stories live in `packages/sb/src/lib/huddle-ui/ChatComposer.stories.tsx`.
337
+
338
+ ## Verification
339
+
340
+ Run these commands after changing the portable chat package:
341
+
342
+ ```sh
343
+ npx nx test huddle-ui
344
+ npx nx build huddle-ui
345
+ npx nx build-storybook sb
346
+ npx nx storybook sb --port 6006
347
+ ```
348
+
349
+ For portability, copy only `packages/huddle-ui/src/lib/chat` into a scratch React/MUI app and confirm no imports point back to the repo-specific `api.service.ts`.
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -0,0 +1,45 @@
1
+ import type { ChatMessageChunkDto, ChatMessageDto } from '../thread';
2
+
3
+ export type MaybePromise<T> = T | Promise<T>;
4
+
5
+ export type ChatStreamAttachmentInput = {
6
+ id: string;
7
+ file: File;
8
+ name: string;
9
+ mimeType?: string;
10
+ size: number;
11
+ lastModified: number;
12
+ };
13
+
14
+ export type ChatStreamMentionInput = {
15
+ id: string;
16
+ providerId: string;
17
+ providerLabel?: string;
18
+ trigger: string;
19
+ displayValue: string;
20
+ insertValue: string;
21
+ description?: string;
22
+ avatarUrl?: string;
23
+ entityType?: string;
24
+ range?: {
25
+ start: number;
26
+ end: number;
27
+ };
28
+ payload?: unknown;
29
+ };
30
+
31
+ export type ChatStreamSendRequest = {
32
+ threadId: string;
33
+ message: ChatMessageDto;
34
+ text: string;
35
+ attachments: readonly ChatStreamAttachmentInput[];
36
+ mentions: readonly ChatStreamMentionInput[];
37
+ signal: AbortSignal;
38
+ metadata?: Record<string, unknown>;
39
+ };
40
+
41
+ export type ChatStreamClient = {
42
+ send: (
43
+ request: ChatStreamSendRequest
44
+ ) => MaybePromise<AsyncIterable<ChatMessageChunkDto>>;
45
+ };