parapet-rails 0.1.0

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 (231) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +48 -0
  4. data/Rakefile +7 -0
  5. data/app/assets/config/parapet/grammar-registry.json +52 -0
  6. data/app/assets/images/parapet/icons/activities/component-inspector.svg +4 -0
  7. data/app/assets/images/parapet/icons/activities/coverage-heatmap.svg +3 -0
  8. data/app/assets/images/parapet/icons/activities/discovery-sidebar.svg +3 -0
  9. data/app/assets/images/parapet/icons/activities/environment-toggles.svg +4 -0
  10. data/app/assets/images/parapet/icons/activities/failure-inspector.svg +5 -0
  11. data/app/assets/images/parapet/icons/activities/flaky-history.svg +3 -0
  12. data/app/assets/images/parapet/icons/activities/flight-recorder.svg +5 -0
  13. data/app/assets/images/parapet/icons/activities/health-graph.svg +3 -0
  14. data/app/assets/images/parapet/icons/activities/injection-engine.svg +3 -0
  15. data/app/assets/images/parapet/icons/activities/live-sql-stream.svg +5 -0
  16. data/app/assets/images/parapet/icons/activities/live-web-preview.svg +4 -0
  17. data/app/assets/images/parapet/icons/activities/monaco-editor.svg +5 -0
  18. data/app/assets/images/parapet/icons/activities/progress-area.svg +3 -0
  19. data/app/assets/images/parapet/icons/activities/registry-metadata.svg +5 -0
  20. data/app/assets/images/parapet/icons/activities/seed-inspector.svg +4 -0
  21. data/app/assets/images/parapet/icons/activities/slow-test-bottlenecks.svg +4 -0
  22. data/app/assets/images/parapet/icons/activities/smart-search.svg +4 -0
  23. data/app/assets/images/parapet/icons/activities/snippet-library.svg +4 -0
  24. data/app/assets/images/parapet/icons/activities/terminal.svg +4 -0
  25. data/app/assets/images/parapet/icons/activities/unified-schema-view.svg +4 -0
  26. data/app/assets/images/parapet/icons/activities/visual-comparison.svg +3 -0
  27. data/app/assets/images/parapet/icons/activities/visual-summary-bar.svg +5 -0
  28. data/app/assets/images/parapet/icons/nav/ideas.svg +4 -0
  29. data/app/assets/images/parapet/icons/nav/languages.svg +8 -0
  30. data/app/assets/images/parapet/icons/nav/profile.svg +4 -0
  31. data/app/assets/images/parapet/icons/nav/pulse.svg +7 -0
  32. data/app/assets/images/parapet/icons/nav/settings.svg +4 -0
  33. data/app/assets/images/parapet/icons/nav/state.svg +5 -0
  34. data/app/assets/images/parapet/icons/nav/styles.svg +7 -0
  35. data/app/assets/images/parapet/icons/nav/tests.svg +4 -0
  36. data/app/assets/images/parapet/icons/nav/traffic.svg +12 -0
  37. data/app/assets/javascripts/parapet/activity-card.js +439 -0
  38. data/app/assets/javascripts/parapet/activity-cards.js +291 -0
  39. data/app/assets/javascripts/parapet/activity-registry.js +181 -0
  40. data/app/assets/javascripts/parapet/activity-toggles.js +76 -0
  41. data/app/assets/javascripts/parapet/application.js +37 -0
  42. data/app/assets/javascripts/parapet/card-failure-inspector.js +134 -0
  43. data/app/assets/javascripts/parapet/card-file-editor.js +1722 -0
  44. data/app/assets/javascripts/parapet/card-progress-area.js +297 -0
  45. data/app/assets/javascripts/parapet/card-smart-search.js +114 -0
  46. data/app/assets/javascripts/parapet/card-snippet-library.js +251 -0
  47. data/app/assets/javascripts/parapet/explorer-resizer.js +92 -0
  48. data/app/assets/javascripts/parapet/parapet_qss.js +342 -0
  49. data/app/assets/javascripts/parapet/proficiency-filter.js +53 -0
  50. data/app/assets/javascripts/parapet/sidebar-resizer.js +197 -0
  51. data/app/assets/javascripts/parapet/styles-workbench.js +819 -0
  52. data/app/assets/javascripts/parapet/tree-logic.js +320 -0
  53. data/app/assets/stylesheets/parapet/_qss_variables.scss +122 -0
  54. data/app/assets/stylesheets/parapet/parapet_qss.scss +69 -0
  55. data/app/assets/stylesheets/parapet/qss_application.css +260940 -0
  56. data/app/assets/stylesheets/parapet/qss_application.css.map +1 -0
  57. data/app/assets/stylesheets/parapet/qss_application.scss +5 -0
  58. data/app/assets/stylesheets/parapet/qss_workspace.scss +209 -0
  59. data/app/assets/stylesheets/parapet/sample.qss +22 -0
  60. data/app/controllers/parapet/application_controller.rb +54 -0
  61. data/app/controllers/parapet/base_controller.rb +18 -0
  62. data/app/controllers/parapet/build_suite/dashboard_controller.rb +10 -0
  63. data/app/controllers/parapet/css_suite/compiler_controller.rb +95 -0
  64. data/app/controllers/parapet/css_suite/dashboard_controller.rb +10 -0
  65. data/app/controllers/parapet/css_suite/preview_controller.rb +46 -0
  66. data/app/controllers/parapet/dashboard_controller.rb +8 -0
  67. data/app/controllers/parapet/drafts_controller.rb +30 -0
  68. data/app/controllers/parapet/explorers_controller.rb +53 -0
  69. data/app/controllers/parapet/files_controller.rb +208 -0
  70. data/app/controllers/parapet/footers_controller.rb +24 -0
  71. data/app/controllers/parapet/intentions_suite/dashboard_controller.rb +10 -0
  72. data/app/controllers/parapet/landing_controller.rb +14 -0
  73. data/app/controllers/parapet/monitor_suite/dashboard_controller.rb +10 -0
  74. data/app/controllers/parapet/pm_suite/publish_controller.rb +16 -0
  75. data/app/controllers/parapet/polyglot_suite/dashboard_controller.rb +10 -0
  76. data/app/controllers/parapet/runner_controller.rb +34 -0
  77. data/app/controllers/parapet/sessions_controller.rb +35 -0
  78. data/app/controllers/parapet/signal_suite/dashboard_controller.rb +10 -0
  79. data/app/controllers/parapet/test_suite/dashboard_controller.rb +10 -0
  80. data/app/controllers/parapet/widgets_controller.rb +33 -0
  81. data/app/helpers/parapet/application_helper.rb +23 -0
  82. data/app/helpers/parapet/icon_helper.rb +57 -0
  83. data/app/jobs/parapet/application_job.rb +4 -0
  84. data/app/mailers/parapet/application_mailer.rb +6 -0
  85. data/app/models/parapet/activity.rb +14 -0
  86. data/app/models/parapet/application_record.rb +5 -0
  87. data/app/models/parapet/current.rb +6 -0
  88. data/app/models/parapet/draft_recovery.rb +9 -0
  89. data/app/models/parapet/internal_record.rb +34 -0
  90. data/app/models/parapet/run_history.rb +22 -0
  91. data/app/models/parapet/runtime_setting.rb +8 -0
  92. data/app/models/parapet/session.rb +7 -0
  93. data/app/models/parapet/suite_assignment.rb +30 -0
  94. data/app/models/parapet/user.rb +12 -0
  95. data/app/services/parapet/activity_phrase_renderer.rb +35 -0
  96. data/app/services/parapet/activity_writer.rb +26 -0
  97. data/app/services/parapet/authorization.rb +198 -0
  98. data/app/services/parapet/consistency_auditor.rb +58 -0
  99. data/app/services/parapet/directory_scanner.rb +159 -0
  100. data/app/services/parapet/draft_recovery_store.rb +18 -0
  101. data/app/services/parapet/explorer_manager.rb +402 -0
  102. data/app/services/parapet/file_save_service.rb +21 -0
  103. data/app/services/parapet/git_side_ref_committer.rb +50 -0
  104. data/app/services/parapet/noun_discovery.rb +26 -0
  105. data/app/services/parapet/publish_gate.rb +83 -0
  106. data/app/services/parapet/test_runner.rb +178 -0
  107. data/app/views/layouts/parapet/application.html.erb +83 -0
  108. data/app/views/layouts/parapet/landing.html.erb +19 -0
  109. data/app/views/parapet/activities/_ai_error_explainer.html.erb +33 -0
  110. data/app/views/parapet/activities/_ai_explainer_widget.html.erb +20 -0
  111. data/app/views/parapet/activities/_calendar_widget.html.erb +31 -0
  112. data/app/views/parapet/activities/_code_snippets_widget.html.erb +22 -0
  113. data/app/views/parapet/activities/_coverage_explorer.html.erb +1 -0
  114. data/app/views/parapet/activities/_dropdown_test.html.erb +20 -0
  115. data/app/views/parapet/activities/_explorer_node.html.erb +102 -0
  116. data/app/views/parapet/activities/_explorer_tree.html.erb +16 -0
  117. data/app/views/parapet/activities/_file_editor.html.erb +183 -0
  118. data/app/views/parapet/activities/_file_explorer.html.erb +23 -0
  119. data/app/views/parapet/activities/_fix_suggestions_widget.html.erb +13 -0
  120. data/app/views/parapet/activities/_generic.html.erb +6 -0
  121. data/app/views/parapet/activities/_live_preview.html.erb +13 -0
  122. data/app/views/parapet/activities/_perspective_filters.html.erb +36 -0
  123. data/app/views/parapet/activities/_progress_area.html.erb +37 -0
  124. data/app/views/parapet/activities/_project_explorer.html.erb +1 -0
  125. data/app/views/parapet/activities/_registry_explorer.html.erb +1 -0
  126. data/app/views/parapet/activities/_run_history.html.erb +2 -0
  127. data/app/views/parapet/activities/_smart_search.html.erb +11 -0
  128. data/app/views/parapet/activities/file_explorer/_perspective_scripts.html.erb +58 -0
  129. data/app/views/parapet/activities/file_explorer/_perspectives.html.erb +0 -0
  130. data/app/views/parapet/activities/file_explorer/_viewport_scripts.html.erb +33 -0
  131. data/app/views/parapet/css_suite/_workbench.html.erb +51 -0
  132. data/app/views/parapet/dashboards/index.html.erb +113 -0
  133. data/app/views/parapet/debug_panel.html.erb +79 -0
  134. data/app/views/parapet/debug_panel_tailwind.html.erb +98 -0
  135. data/app/views/parapet/erb_patch_test.html.erb +5 -0
  136. data/app/views/parapet/explorers/show.html.erb +1 -0
  137. data/app/views/parapet/git_viewer.html.erb +62 -0
  138. data/app/views/parapet/git_viewer_after.html.erb +14 -0
  139. data/app/views/parapet/git_viewer_before.html.erb +14 -0
  140. data/app/views/parapet/git_viewer_governed.html.erb +88 -0
  141. data/app/views/parapet/git_viewer_governed_after.html.erb +36 -0
  142. data/app/views/parapet/git_viewer_governed_before.html.erb +36 -0
  143. data/app/views/parapet/git_viewer_tailwind.html.erb +88 -0
  144. data/app/views/parapet/git_viewer_thin_wrapper.html.erb +85 -0
  145. data/app/views/parapet/hierarchical_layout_test.html.erb +85 -0
  146. data/app/views/parapet/hybrid_stress_test.html.erb +25 -0
  147. data/app/views/parapet/index.html.erb +35 -0
  148. data/app/views/parapet/mutation_stress_test.html.erb +82 -0
  149. data/app/views/parapet/recursive_anchoring_test.html.erb +66 -0
  150. data/app/views/parapet/sessions/new.html.erb +28 -0
  151. data/app/views/parapet/shared/_cad_inspector.html.erb +92 -0
  152. data/app/views/parapet/shared/_discovery_sidebar.html.erb +48 -0
  153. data/app/views/parapet/shared/_explorer_bar.html.erb +71 -0
  154. data/app/views/parapet/shared/_footer.html.erb +10 -0
  155. data/app/views/parapet/shared/_footer_ideas.html.erb +36 -0
  156. data/app/views/parapet/shared/_footer_languages.html.erb +33 -0
  157. data/app/views/parapet/shared/_footer_pulse.html.erb +33 -0
  158. data/app/views/parapet/shared/_footer_state.html.erb +33 -0
  159. data/app/views/parapet/shared/_footer_styles.html.erb +37 -0
  160. data/app/views/parapet/shared/_footer_tests.html.erb +39 -0
  161. data/app/views/parapet/shared/_footer_traffic.html.erb +45 -0
  162. data/app/views/parapet/shared/_global_nav.html.erb +55 -0
  163. data/app/views/parapet/shared/_head.html.erb +4 -0
  164. data/app/views/parapet/shared/_header.html.erb +20 -0
  165. data/app/views/parapet/shared/_object_inspector.html.erb +76 -0
  166. data/app/views/parapet/shared/_ribbon_header.html.erb +3 -0
  167. data/app/views/parapet/shared/_root_selector_modal.html.erb +15 -0
  168. data/app/views/parapet/shared/_tab_bar.html.erb +62 -0
  169. data/app/views/parapet/shared/activities_ribbon/_agent_group.html.erb +15 -0
  170. data/app/views/parapet/shared/activities_ribbon/_analysis_group.html.erb +15 -0
  171. data/app/views/parapet/shared/activities_ribbon/_architect_group.html.erb +21 -0
  172. data/app/views/parapet/shared/activities_ribbon/_explorer_group.html.erb +12 -0
  173. data/app/views/parapet/shared/activities_ribbon/_inspector_group.html.erb +15 -0
  174. data/app/views/parapet/shared/activities_ribbon/_proficiency_filter.html.erb +24 -0
  175. data/app/views/parapet/shared/activities_ribbon/_runner_group.html.erb +21 -0
  176. data/app/views/parapet/shared/activities_ribbon/_spatial_inspector_group.html.erb +18 -0
  177. data/app/views/parapet/shared/activities_ribbon/_studio_group.html.erb +18 -0
  178. data/app/views/parapet/shared/activities_ribbon/_stylist_group.html.erb +18 -0
  179. data/app/views/parapet/shared/explorers/_base.html.erb +107 -0
  180. data/app/views/parapet/shared/explorers/_css.html.erb +1 -0
  181. data/app/views/parapet/shared/explorers/_intentions.html.erb +1 -0
  182. data/app/views/parapet/shared/explorers/_project.html.erb +1 -0
  183. data/app/views/parapet/shared/explorers/_tests.html.erb +1 -0
  184. data/app/views/parapet/shared/head/_meta.html.erb +3 -0
  185. data/app/views/parapet/shared/head/_styles_activities.html.erb +152 -0
  186. data/app/views/parapet/shared/head/_styles_shared.html.erb +52 -0
  187. data/app/views/parapet/shared/head/_styles_workspace.html.erb +67 -0
  188. data/app/views/parapet/shared/ribbon_header/_diagnostic_controls.html.erb +17 -0
  189. data/app/views/parapet/shared/ribbon_header/_execution_controls.html.erb +28 -0
  190. data/app/views/parapet/shared/ribbon_header/_focus_search.html.erb +5 -0
  191. data/app/views/parapet/shared/root_selector/_header.html.erb +10 -0
  192. data/app/views/parapet/shared/root_selector/_path_input.html.erb +13 -0
  193. data/app/views/parapet/shared/root_selector/_quick_select.html.erb +20 -0
  194. data/app/views/parapet/shared/root_selector/_scripts.html.erb +61 -0
  195. data/app/views/parapet/shared/root_selector/_warning.html.erb +11 -0
  196. data/app/views/parapet/shared/widgets/_base.html.erb +6 -0
  197. data/app/views/parapet/shared/widgets/_css.html.erb +79 -0
  198. data/app/views/parapet/widgets/_explainer.html.erb +17 -0
  199. data/app/views/parapet/widgets/_hotkeys.html.erb +20 -0
  200. data/app/views/parapet/widgets/_search.html.erb +68 -0
  201. data/app/views/parapet/widgets/_spatial_inspector.html.erb +54 -0
  202. data/app/views/parapet/widgets/_widget-explainer.html.erb +17 -0
  203. data/app/views/parapet/widgets/_widget-hotkeys.html.erb +20 -0
  204. data/app/views/parapet/widgets/_widget-search.html.erb +68 -0
  205. data/app/views/parapet/widgets/_widget-snippets.html.erb +18 -0
  206. data/app/views/parapet/widgets/_widget-suggestions.html.erb +15 -0
  207. data/app/views/parapet/widgets/show.html.erb +3 -0
  208. data/app/views/shared/_footer.html.erb +0 -0
  209. data/config/parapet/qss_config.yml +117 -0
  210. data/config/parapet/qss_registry.yml +14 -0
  211. data/config/routes.rb +40 -0
  212. data/db/migrate/20260916035300_create_run_histories.rb +16 -0
  213. data/db/migrate/20261002000000_expand_run_histories_for_activity_tracking.rb +7 -0
  214. data/db/parapet_migrate/20260929184200_create_parapet_users.rb +12 -0
  215. data/db/parapet_migrate/20260929184300_create_parapet_sessions.rb +9 -0
  216. data/db/parapet_migrate/20260930000000_create_parapet_runtime_settings.rb +12 -0
  217. data/db/parapet_migrate/20260930000100_create_parapet_suite_assignments.rb +14 -0
  218. data/db/parapet_migrate/20261002000000_create_parapet_activities.rb +16 -0
  219. data/db/parapet_migrate/20261002000100_create_parapet_draft_recoveries.rb +13 -0
  220. data/db/parapet_schema.rb +64 -0
  221. data/db/schema.rb +21 -0
  222. data/db/schema.released.rb +0 -0
  223. data/lib/parapet/engine.rb +11 -0
  224. data/lib/parapet/environment_guard.rb +14 -0
  225. data/lib/parapet/version.rb +3 -0
  226. data/lib/parapet-rails.rb +1 -0
  227. data/lib/parapet.rb +59 -0
  228. data/lib/tasks/parapet_qss.rake +26 -0
  229. data/lib/tasks/parapet_tasks.rake +25 -0
  230. data/lib/tasks/randomize_explorer_health.rb +101 -0
  231. metadata +423 -0
@@ -0,0 +1,178 @@
1
+ require 'open3'
2
+
3
+ module Parapet
4
+ class TestRunner
5
+ attr_reader :root_path
6
+
7
+ def initialize(root_path = nil, user: Current.user, performed_via: "human")
8
+ @root_path = root_path || Parapet::Engine.root.to_s
9
+ @user = user
10
+ @performed_via = performed_via
11
+ end
12
+
13
+ def detect_frameworks
14
+ frameworks = []
15
+
16
+ frameworks << { id: 'rspec', name: 'RSpec', color: 'red' } if has_rspec?
17
+ frameworks << { id: 'minitest', name: 'Minitest', color: 'blue' } if has_minitest?
18
+ frameworks << { id: 'cucumber', name: 'Cucumber', color: 'emerald' } if has_cucumber?
19
+
20
+ # Quality/Security Tools
21
+ frameworks << { id: 'rubocop', name: 'RuboCop', color: 'slate' } if has_config?('.rubocop.yml')
22
+ frameworks << { id: 'standardrb', name: 'Standard', color: 'slate' } if has_config?('.standard.yml')
23
+ frameworks << { id: 'brakeman', name: 'Brakeman', color: 'orange' } if has_gem?('brakeman')
24
+ frameworks << { id: 'zeitwerk', name: 'Zeitwerk', color: 'cyan' } if has_config?('config/environments/development.rb')
25
+
26
+ frameworks
27
+ end
28
+
29
+ def execute(command_type: nil, path: nil)
30
+ argv = determine_command(command_type)
31
+ argv = argv + [ path ] if path.present?
32
+ full_command = argv.join(" ")
33
+ run_history = RunHistory.create!(
34
+ run_type: command_type.presence || "default",
35
+ command: full_command,
36
+ status: "running",
37
+ triggered_by_actor_type: "human",
38
+ user_id: @user&.id,
39
+ performed_via: @performed_via,
40
+ started_at: Time.current
41
+ )
42
+
43
+ begin
44
+ stdout, stderr, status = Open3.capture3(*argv, chdir: @root_path)
45
+ combined_output = stdout + stderr
46
+ rescue StandardError => error
47
+ run_history.update!(
48
+ status: "failed",
49
+ raw_output: error.message,
50
+ finished_at: Time.current
51
+ )
52
+ raise
53
+ end
54
+
55
+ run_history.update!(
56
+ status: status.exitstatus == 0 ? "passed" : "failed",
57
+ exit_code: status.exitstatus,
58
+ raw_output: combined_output,
59
+ finished_at: Time.current
60
+ )
61
+ ActivityWriter.run!(
62
+ run_history: run_history,
63
+ user: @user,
64
+ action: "test_run",
65
+ performed_via: @performed_via
66
+ )
67
+
68
+ {
69
+ command: full_command,
70
+ output: parse_output(combined_output),
71
+ summary: extract_summary(combined_output),
72
+ exit_code: status.exitstatus
73
+ }
74
+ end
75
+
76
+ private
77
+
78
+ def determine_command(type)
79
+ case type&.to_sym
80
+ when :rspec then %w[bundle exec rspec]
81
+ when :cucumber then %w[bundle exec cucumber]
82
+ when :rubocop then %w[bundle exec rubocop]
83
+ when :standardrb then %w[bundle exec standard]
84
+ when :brakeman then %w[bundle exec brakeman]
85
+ when :zeitwerk then %w[bundle exec rails zeitwerk:check]
86
+ else
87
+ default_framework_command
88
+ end
89
+ end
90
+
91
+ def default_framework_command
92
+ Parapet.test_framework.to_sym == :rspec ? %w[bundle exec rspec] : %w[bundle exec rails test]
93
+ end
94
+
95
+ def parse_output(output)
96
+ output.split("\n").map do |line|
97
+ parse_line(line)
98
+ end
99
+ end
100
+
101
+ def parse_line(line)
102
+ data = { text: line, type: 'info' }
103
+
104
+ # RSpec / General Ruby backtrace/failure pattern: path/to/file.rb:123:in `method'
105
+ if line =~ /^(.+?):(\d+):/
106
+ file, line_num = $1, $2
107
+ if File.exist?(File.join(@root_path, file))
108
+ data[:type] = 'fail'
109
+ data[:file] = file
110
+ data[:line] = line_num.to_i
111
+ data[:message] = line.split(':', 3).last.strip
112
+ return data
113
+ end
114
+ end
115
+
116
+ # RuboCop pattern
117
+ if line.match(/^(.+?):(\d+):(\d+): ([A-Z]): (.+)$/)
118
+ data[:type] = 'lint'
119
+ data[:file], data[:line], data[:message] = $1, $2.to_i, $5
120
+ return data
121
+ end
122
+
123
+ # Progress indicators
124
+ if line.match(/^[.FE*]+$/) && line.length < 100
125
+ data[:type] = 'pass'
126
+ return data
127
+ end
128
+
129
+ # Summary lines
130
+ if line.include?('examples') || line.include?('scenarios') || line.include?('inspected') || line.include?('Finished')
131
+ data[:type] = 'summary'
132
+ end
133
+
134
+ data
135
+ end
136
+
137
+ def extract_summary(output)
138
+ summary = { runs: 0, failures: 0, errors: 0 }
139
+
140
+ if output =~ /(\d+) examples?, (\d+) failures?/
141
+ summary[:runs] = $1.to_i
142
+ summary[:failures] = $2.to_i
143
+ elsif output =~ /(\d+) scenarios? \((\d+) failed/
144
+ summary[:runs] = $1.to_i
145
+ summary[:failures] = $2.to_i
146
+ elsif output =~ /(\d+) files inspected, (\d+) offenses detected/
147
+ summary[:runs] = $1.to_i
148
+ summary[:failures] = $2.to_i
149
+ end
150
+
151
+ summary
152
+ end
153
+
154
+ def has_rspec?
155
+ Dir.exist?(File.join(@root_path, 'spec'))
156
+ end
157
+
158
+ def has_minitest?
159
+ Dir.exist?(File.join(@root_path, 'test'))
160
+ end
161
+
162
+ def has_cucumber?
163
+ Dir.exist?(File.join(@root_path, 'features'))
164
+ end
165
+
166
+ def has_config?(path)
167
+ File.exist?(File.join(@root_path, path))
168
+ end
169
+
170
+ def has_gem?(name)
171
+ gemfile_path = File.join(@root_path, 'Gemfile')
172
+ return false unless File.exist?(gemfile_path)
173
+ File.readlines(gemfile_path).any? { |l| l.include?(name) }
174
+ rescue
175
+ false
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,83 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Parapet IDE</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <%= yield :head %>
8
+
9
+ <!-- Global Styles -->
10
+ <link rel="stylesheet" href="/css_suite/live?t=<%= Time.now.to_i %>" media="all">
11
+ <%= stylesheet_link_tag "parapet/qss_application", media: "all" %>
12
+ <%= javascript_include_tag "qss/qss-engine", defer: true %>
13
+ <%= javascript_include_tag "parapet/parapet_qss", defer: true %>
14
+ <%= javascript_include_tag "parapet/tree-logic", defer: true %>
15
+ <%= javascript_include_tag "parapet/sidebar-resizer", defer: true %>
16
+ <%= javascript_include_tag "parapet/explorer-resizer", defer: true %>
17
+ <%= javascript_include_tag "parapet/activity-card", defer: true %>
18
+ <%= javascript_include_tag "parapet/activity-cards", defer: true %>
19
+ <%= javascript_include_tag "parapet/activity-registry", defer: true %>
20
+ <%= javascript_include_tag "parapet/card-failure-inspector", defer: true %>
21
+ <%= javascript_include_tag "parapet/card-file-editor", defer: true %>
22
+ <%= javascript_include_tag "parapet/card-progress-area", defer: true %>
23
+ <%= javascript_include_tag "parapet/card-smart-search", defer: true %>
24
+ <%= javascript_include_tag "parapet/card-snippet-library", defer: true %>
25
+ <%= javascript_include_tag "parapet/activity-toggles", defer: true %>
26
+ <%= javascript_include_tag "parapet/proficiency-filter", defer: true %>
27
+ <%= javascript_include_tag "parapet/styles-workbench", defer: true %>
28
+ <%= javascript_include_tag "parapet/application", defer: true %>
29
+
30
+ <script>
31
+ document.addEventListener('DOMContentLoaded', () => {
32
+ if (typeof QSSEngine !== 'undefined') {
33
+ QSSEngine.startPulse();
34
+ QSSEngine.startObserver();
35
+ QSSEngine.syncRoot();
36
+ QSSEngine.syncPerspective();
37
+ QSSEngine.startVisualSyncing();
38
+ }
39
+ });
40
+ </script>
41
+
42
+ <!-- Custom Version_3 Header Styles/Metas -->
43
+ <%= render "parapet/shared/head" %>
44
+ </head>
45
+ <body id="pp-main-root" class="qss bg-brand-main" style="contain: layout; overflow: hidden; width: 100vw; height: 100vh;">
46
+ <div id="qss-canvas" class="qss-dampen-scaling">
47
+ <div id="pp-header" class="qss bg-brand-header border-b border-slate-800 contain-layout" style="left: 0; top: 0; width: 100%; height: var(--header-h);">
48
+ <%= render "parapet/shared/header" %>
49
+ </div>
50
+ <%= render "parapet/shared/global_nav" %>
51
+
52
+ <% if controller.class.name.include?("Suite") || controller.class.name.include?("DashboardController") %>
53
+ <%= render "parapet/shared/explorer_bar" %>
54
+
55
+ <div id="pp-workspace-content" class="qss bg-brand-main contain-layout" style="left: calc(var(--global-nav-w, var(--global-nav-w-default)) + var(--explorer-bar-w, var(--explorer-bar-offset-default))); top: var(--header-h); right: var(--inspector-bar-w, var(--inspector-bar-w-default)); bottom: var(--code-source-h, var(--code-source-h-default));">
56
+ <%= yield %>
57
+ </div>
58
+
59
+ <div id="pp-code-source" class="qss bg-brand-header border-t border-r border-slate-800 contain-layout" style="left: calc(var(--global-nav-w, var(--global-nav-w-default)) + var(--explorer-bar-w, var(--explorer-bar-offset-default))); right: calc(var(--inspector-bar-w, var(--inspector-bar-w-default)) + (100% - (var(--global-nav-w, var(--global-nav-w-default)) + var(--explorer-bar-w, var(--explorer-bar-offset-default)) + var(--inspector-bar-w, var(--inspector-bar-w-default)))) / 2); bottom: var(--footer-h, var(--footer-h-default)); height: var(--code-source-h, var(--code-source-h-default));">
60
+ <!-- Source Code View -->
61
+ </div>
62
+
63
+ <div id="pp-code-compiled" class="qss bg-brand-header border-t border-slate-800 contain-layout" style="left: calc(var(--global-nav-w, var(--global-nav-w-default)) + var(--explorer-bar-w, var(--explorer-bar-offset-default)) + (100% - (var(--global-nav-w, var(--global-nav-w-default)) + var(--explorer-bar-w, var(--explorer-bar-offset-default)) + var(--inspector-bar-w, var(--inspector-bar-w-default)))) / 2); right: var(--inspector-bar-w, var(--inspector-bar-w-default)); bottom: var(--footer-h, var(--footer-h-default)); height: var(--code-source-h, var(--code-source-h-default));">
64
+ <!-- Compiled Output View -->
65
+ </div>
66
+
67
+ <div id="pp-inspector-bar" class="qss bg-brand-main border-l border-slate-800 vertical contain-layout" style="right: 0; top: var(--header-h); width: var(--inspector-bar-w, var(--inspector-bar-w-default)); bottom: var(--footer-h, var(--footer-h-default));">
68
+ <%= render "parapet/shared/object_inspector" %>
69
+ </div>
70
+ <% else %>
71
+ <div id="pp-main-stage" class="qss bg-brand-main contain-layout" style="left: var(--global-nav-w, var(--global-nav-w-default)); top: var(--header-h); right: 0; bottom: var(--footer-h, var(--footer-h-default));">
72
+ <%= yield %>
73
+ </div>
74
+ <% end %>
75
+
76
+ <div id="pp-footer" class="qss bg-brand-header border-t border-slate-800 contain-layout" style="left: 0; bottom: 0; width: 100%; height: var(--footer-h, var(--footer-h-default));">
77
+ <%= render "parapet/shared/footer" %>
78
+ </div>
79
+ </div>
80
+
81
+ <div id="pp-portal-root" class="qss-absolute qss-inset-0 qss-pointer-events-none qss-z-[1000]"></div>
82
+ </body>
83
+ </html>
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Parapet | Login</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <%= yield :head %>
8
+
9
+ <!-- Global Styles -->
10
+ <%= stylesheet_link_tag "parapet/qss_application", media: "all" %>
11
+
12
+ <%= render "parapet/shared/head" %>
13
+ </head>
14
+ <body class="qss-bg-brand-main qss-overflow-hidden">
15
+ <div id="qss-canvas">
16
+ <%= yield %>
17
+ </div>
18
+ </body>
19
+ </html>
@@ -0,0 +1,33 @@
1
+ <div class="qss anchored vertical h-full bg-brand-main p[16] gap[16]">
2
+ <div class="qss anchored horizontal v-mid gap[12]">
3
+ <div class="qss anchored">
4
+ <div class="qss anchored w[40] h[40] rounded[8] bg-indigo-500/20 border[1] border-indigo-500/30 horizontal v-mid centered">
5
+ <i class="lucide-bot w[24] h[24] text-indigo-400"></i>
6
+ </div>
7
+ <div class="qss absolute pos-r[-4] pos-t[-4] w[12] h[12] bg-emerald-500 border[2] border-brand-main rounded-full"></div>
8
+ </div>
9
+ <div class="qss anchored vertical">
10
+ <div class="qss anchored text-size[11] weight[700] uppercase tracking[widest] text-white">Why did this fail?</div>
11
+ <div class="qss anchored px[6] py[2] rounded[4] bg-indigo-500/10 border[1] border-indigo-500/20 text-size[8] weight[900] text-indigo-400 uppercase tracking[tighter]">AI Assistant</div>
12
+ </div>
13
+ </div>
14
+ <div class="qss anchored vertical gap[8]">
15
+ <div class="qss anchored horizontal v-mid gap[6] text-red-400">
16
+ <i class="lucide-alert-triangle w[14] h[14]"></i>
17
+ <span class="qss anchored text-size[10] weight[700] uppercase tracking[tight]">RSpec Failure</span>
18
+ </div>
19
+ <div class="qss anchored p[10] rounded[4] bg-red-500/5 border[1] border-red-500/10 font[mono] text-size[10] leading-relaxed">
20
+ <span class="qss anchored text-red-400 weight[700]">FAILED.</span><br/>
21
+ <span class="qss anchored text-slate-400 italic">spec/models/user_spec.rb:42:</span> Expected <span class="qss anchored text-slate-300">user.items.count</span> to be <span class="qss anchored text-white weight[700]">5</span>, but got <span class="qss anchored text-red-400 weight[700]">3</span>.
22
+ </div>
23
+ </div>
24
+ <div class="qss anchored vertical gap[6]">
25
+ <div class="qss anchored horizontal v-mid gap[6] text-indigo-400">
26
+ <i class="lucide-brain-circuit w[14] h[14]"></i>
27
+ <span class="qss anchored text-size[10] weight[700] uppercase tracking[tight]">AI Interpretation</span>
28
+ </div>
29
+ <p class="qss anchored text-size[11] text-slate-400 leading-normal ml[20]">
30
+ This test failed because it expected the <code class="qss anchored text-slate-200">user</code> object to have exactly 5 associated items, but it only found 3 items.
31
+ </p>
32
+ </div>
33
+ </div>
@@ -0,0 +1,20 @@
1
+ <turbo-frame id="active-widget">
2
+ <div class="qss-p-3 qss-flex qss-flex-col qss-h-full qss-overflow-hidden">
3
+ <div class="qss-flex qss-items-center qss-gap-2 qss-mb-2">
4
+ <i class="lucide-bot qss-w-3.5 qss-h-3.5 qss-text-indigo-400"></i>
5
+ <span class="qss-text-[10px] qss-font-bold qss-uppercase qss-tracking-widest qss-text-slate-400">AI Error Explainer</span>
6
+ </div>
7
+ <div class="qss-flex-1 qss-overflow-y-auto custom-scrollbar">
8
+ <div class="qss-bg-indigo-500/5 qss-border qss-border-indigo-500/10 qss-rounded qss-p-2 qss-mb-2">
9
+ <p class="qss-text-[10px] qss-text-slate-400 qss-leading-relaxed qss-italic">
10
+ Analyzing current context... Waiting for test failure or selection.
11
+ </p>
12
+ </div>
13
+ <div class="qss-space-y-2 qss-opacity-40 qss-grayscale qss-pointer-events-none">
14
+ <div class="qss-h-2 qss-bg-slate-800 qss-rounded qss-w-3/4"></div>
15
+ <div class="qss-h-2 qss-bg-slate-800 qss-rounded qss-w-1/2"></div>
16
+ <div class="qss-h-2 qss-bg-slate-800 qss-rounded qss-w-2/3"></div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </turbo-frame>
@@ -0,0 +1,31 @@
1
+ <div id="pp-calendar-card" class="qss qss-pos-x[center] qss-pos-y[center] qss-w[714] qss-h[600] qss-bg-brand-main qss-border[1] qss-border-slate-800 qss-depth[ui]">
2
+ <!-- Header -->
3
+ <div id="pp-calendar-header" class="qss qss-w-pct[1000] qss-h[50] qss-border-b[1] qss-border-slate-800 qss-flex qss-items-center qss-px[20]">
4
+ <span class="qss qss-text-indigo-500 qss-font-bold qss-text-size[18]">AUGUST 2026</span>
5
+ </div>
6
+
7
+ <!-- The Grid Container (Relative for local children) -->
8
+ <div id="pp-calendar-body" class="qss qss-pos-y[50] qss-w-pct[1000] qss-h-pct[900] qss-relative">
9
+ <% qss.grid(31, cols: 7, w: 100, h: 100, gap_x: 2, gap_y: 2).each do |day| %>
10
+ <div id="day-<%= day.index %>"
11
+ class="qss qss-pos-x[<%= day.x %>] qss-pos-y[<%= day.y %>] qss-w[<%= day.w %>] qss-h[<%= day.h %>] qss-bg-slate-900 qss-hover:bg-slate-800 qss-border[1] qss-border-slate-800">
12
+
13
+ <span class="qss qss-pos-x[8] qss-pos-y[8] qss-text-slate-500 qss-text-size[10] qss-font-mono">
14
+ <%= day.index.to_s.rjust(2, '0') %>
15
+ </span>
16
+
17
+ <% if day.index == 19 %>
18
+ <!-- Deterministic Marker -->
19
+ <div class="qss qss-pos-x[center] qss-pos-y[center] qss-w[60] qss-h[4] qss-bg-indigo-500 qss-rounded[2]"></div>
20
+ <% end %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+
25
+ <!-- Footer (Metadata) -->
26
+ <div id="pp-calendar-footer" class="qss qss-pos-y[bottom-0] qss-w-pct[1000] qss-h[30] qss-bg-slate-900 qss-flex qss-items-center qss-px[10]">
27
+ <span class="qss qss-text-slate-600 qss-text-size[9] qss-font-mono">
28
+ GRID: 7x5 | UNIT: 100x100 | GAP: 2
29
+ </span>
30
+ </div>
31
+ </div>
@@ -0,0 +1,22 @@
1
+ <turbo-frame id="active-widget">
2
+ <div class="qss-p-3 qss-flex qss-flex-col qss-h-full qss-overflow-hidden">
3
+ <div class="qss-flex qss-items-center qss-gap-2 qss-mb-2">
4
+ <i class="lucide-code qss-w-3.5 qss-h-3.5 qss-text-blue-400"></i>
5
+ <span class="qss-text-[10px] qss-font-bold qss-uppercase qss-tracking-widest qss-text-slate-400">Code Snippets</span>
6
+ </div>
7
+ <div class="qss-flex-1 qss-overflow-y-auto custom-scrollbar qss-space-y-2">
8
+ <div class="qss-group qss-cursor-pointer">
9
+ <div class="qss-text-[9px] qss-text-slate-500 group-hover:qss-text-blue-400 qss-transition-colors qss-uppercase qss-font-mono">expect().to receive()</div>
10
+ <div class="qss-bg-black/20 qss-rounded qss-p-1.5 qss-border qss-border-slate-800/50 qss-mt-1">
11
+ <code class="qss-text-[9px] qss-text-slate-300">expect(obj).to receive(:msg)</code>
12
+ </div>
13
+ </div>
14
+ <div class="qss-group qss-cursor-pointer">
15
+ <div class="qss-text-[9px] qss-text-slate-500 group-hover:qss-text-blue-400 qss-transition-colors qss-uppercase qss-font-mono">let! { ... }</div>
16
+ <div class="qss-bg-black/20 qss-rounded qss-p-1.5 qss-border qss-border-slate-800/50 qss-mt-1">
17
+ <code class="qss-text-[9px] qss-text-slate-300">let!(:user) { create(:user) }</code>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </turbo-frame>
@@ -0,0 +1 @@
1
+ <%= render partial: 'parapet/activities/explorer_base', locals: { type: 'coverage', tree_data: @tree_data } %>
@@ -0,0 +1,20 @@
1
+ <% dropdown = qss.object(:Dropdown) %>
2
+ <% list_h = qss.resolve_height(dropdown, :list, rows: 4) %>
3
+
4
+ <div id="pp-theme-select" class="qss qss-pos-x[center] qss-pos-y[100] qss-w[200] qss-h[40] qss-relative">
5
+ <!-- Header Slot -->
6
+ <div id="pp-theme-select-header" class="qss qss-w-pct[1000] qss-h-pct[1000] qss-bg-slate-800 qss-border[1] qss-border-slate-700 qss-rounded[4] qss-px[12] qss-flex qss-items-center qss-justify-between qss-cursor-pointer">
7
+ <span class="qss-text-slate-300 qss-text-size[14]">Select Theme...</span>
8
+ <svg class="qss-w[12] qss-h[12] qss-text-slate-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
9
+ </div>
10
+
11
+ <!-- List Slot (Positioned via Blueprint pos: [0, 42]) -->
12
+ <div id="pp-theme-select-list"
13
+ class="qss qss-pos-x[0] qss-pos-y[42] qss-w[200] qss-h[<%= list_h %>] qss-depth[overlay] qss-bg-brand-main qss-border[1] qss-border-slate-800 qss-rounded[4] qss-overflow-y-auto">
14
+ <% ["Midnight", "Emerald", "High Contrast", "Material"].each_with_index do |theme, i| %>
15
+ <div class="qss-relative qss-w-full qss-h[32] qss-px[12] qss-flex qss-items-center hover:qss-bg-slate-800 qss-cursor-pointer">
16
+ <span class="qss-text-slate-400 qss-text-size[13]"><%= theme %></span>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,102 @@
1
+ <%# node: JSON object, depth: integer %>
2
+ <%
3
+ is_dir = node['type'] == 'directory'
4
+ has_children = node['children'] && node['children'].any?
5
+ metadata = node['metadata'] || {}
6
+ is_phantom = metadata['is_phantom']
7
+
8
+ icon = node['icon'] || (is_dir ? 'icon-folder' : 'icon-file')
9
+ icon_color = node['iconColor'] ? node['iconColor'].gsub('qss-', '') : 'text-slate-500'
10
+
11
+ if is_phantom
12
+ icon_color = 'text-slate-700' # Muted icon for hidden elements
13
+ end
14
+
15
+ node_status = node['status']
16
+ status_class = case node_status
17
+ when 'red' then 'bg-red-500 shadow[sm]'
18
+ when 'green' then 'bg-emerald-500 shadow[sm]'
19
+ when 'orange' then 'bg-amber-500 shadow[sm]'
20
+ when 'grey' then 'bg-slate-600'
21
+ else 'bg-slate-700'
22
+ end
23
+
24
+ node_health = node['health']
25
+ %>
26
+
27
+ <div class="qss qss-explorer-node anchored vertical"
28
+ data-path="<%= node['path'] %>"
29
+ data-type="<%= node['type'] %>"
30
+ draggable="<%= !is_dir %>"
31
+ style="--depth: <%= depth %>">
32
+ <div class="qss anchored horizontal v-mid py[1] px[2] hover:bg-white/5 clickable smooth qss-tree-row group/row">
33
+ <!-- Indentation & Icons -->
34
+ <div class="anchored horizontal v-mid" style="margin-left: <%= depth * 12 %>px;">
35
+ <div class="anchored w[12] h[12] centered">
36
+ <% if is_dir && has_children %>
37
+ <i class="icon-chevron-right text-slate-500 group-hover/row:text-slate-300 transition-all chevron-icon" style="font-size: 6px;"></i>
38
+ <% end %>
39
+ </div>
40
+ <div class="anchored w[12] h[12] centered">
41
+ <i class="<%= icon %> <%= icon_color %> centered opacity[1000]" style="font-size: 10px;"></i>
42
+ </div>
43
+ <% if metadata['is_staged'] %>
44
+ <i class="icon-circle-dot text-amber-500 text-size[5] ml[2] animate-pulse" title="Staged Changes"></i>
45
+ <% end %>
46
+ </div>
47
+
48
+ <!-- Name & Syntax (Grammar/Librarian) -->
49
+ <div class="anchored vertical ml[2] truncate">
50
+ <span class="anchored text-size[4] font[sans] <%= is_dir ?'font-medium text-slate-300' : 'text-slate-400' %>">
51
+ <%= node['name'] %>
52
+ </span>
53
+ <% if metadata['syntax'] %>
54
+ <span class="anchored text-size[3] font[mono] text-indigo-400 opacity[800]">
55
+ <%= metadata['syntax'] %>
56
+ </span>
57
+ <% end %>
58
+ </div>
59
+
60
+ <!-- Status Indicator (for files) -->
61
+ <% if !is_dir && node_status && !metadata['syntax'] %>
62
+ <div class="anchored ml-auto horizontal v-mid pr[4] gap[6]">
63
+ <% if is_phantom %>
64
+ <div class="anchored clickable group/eye" onclick="QSSEngine.revealPhantom('<%= node['name'] %>', event)" title="Reveal Phantom Object">
65
+ <i class="icon-eye text-slate-600 group-hover/eye:text-indigo-400 text-size[7] smooth"></i>
66
+ </div>
67
+ <% end %>
68
+ <div class="anchored clickable group/focus" onclick="QSSEngine.toggleFocusMode('<%= node['path'] %>', event)" title="Focus Mode">
69
+ <i class="icon-maximize-2 text-slate-600 group-hover/focus:text-emerald-400 text-size[7] smooth"></i>
70
+ </div>
71
+ <div class="anchored w[6] h[6] rounded-full <%= status_class %>"></div>
72
+ </div>
73
+ <% end %>
74
+
75
+ <!-- Health Bar (for folders) -->
76
+ <% if is_dir && node_health %>
77
+ <div class="anchored ml-auto horizontal v-mid w[40] h[4] bg-white/5 rounded-full overflow-hidden">
78
+ <% node_health.each do |status, percent| %>
79
+ <%
80
+ bar_color = case status
81
+ when 'red' then 'bg-red-500'
82
+ when 'green' then 'bg-emerald-500'
83
+ when 'orange' then 'bg-amber-500'
84
+ when 'grey' then 'bg-slate-500'
85
+ else 'bg-slate-500'
86
+ end
87
+ %>
88
+ <div class="anchored h-full <%= bar_color %>" style="width: <%= percent %>%"></div>
89
+ <% end %>
90
+ </div>
91
+ <% end %>
92
+ </div>
93
+
94
+ <!-- Children -->
95
+ <% if has_children %>
96
+ <div class="qss anchored vertical qss-children-container">
97
+ <% node['children'].each do |child| %>
98
+ <%= render partial: 'parapet/activities/explorer_node', locals: { node: child, depth: depth + 1 } %>
99
+ <% end %>
100
+ </div>
101
+ <% end %>
102
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="anchored vertical" id="qss-tree-container">
2
+ <% if tree_data %>
3
+ <% if tree_data[:path] && tree_data[:path].start_with?('/') && tree_data[:path] != '/' %>
4
+ <%# This is a virtual root or a focused root, render it as the top node %>
5
+ <%= render partial: 'parapet/activities/explorer_node', locals: { node: tree_data.deep_stringify_keys, depth: 0 } %>
6
+ <% elsif tree_data[:children] %>
7
+ <% tree_data[:children].each do |node| %>
8
+ <%= render partial: 'parapet/activities/explorer_node', locals: { node: node.deep_stringify_keys, depth: 0 } %>
9
+ <% end %>
10
+ <% end %>
11
+ <% else %>
12
+ <div class="anchored p[16] text-size[9] text-slate-500 italic opacity[500]">
13
+ No files found in <%= @root_path %>
14
+ </div>
15
+ <% end %>
16
+ </div>