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,5 @@
1
+ @import "qss_variables"; // Parapet's QSS settings, before core so they win over core's !default values
2
+ @import "qss/qss_core"; // from the qss gem (QSS.stylesheets_path on the Sass load path)
3
+ @import "parapet_qss";
4
+ @import "qss_workspace";
5
+ /* Cache Buster: <%= Time.now.to_i %> */
@@ -0,0 +1,209 @@
1
+ :root {
2
+ --header-h: 60px;
3
+ --novice: #10B981;
4
+ --intermediate: #F59E0B;
5
+ --advanced: #EF4444;
6
+ --power: #8B5CF6;
7
+ }
8
+
9
+ /* Structural Layout Integrity */
10
+ #pp-main-root,
11
+ #pp-header,
12
+ #pp-header-inner,
13
+ #pp-header-branding,
14
+ #pp-header-tagline,
15
+ #pp-header-ribbon,
16
+ #pp-workspace-layers,
17
+ #pp-workspace-content,
18
+ #pp-inspector-bar,
19
+ #pp-code-source,
20
+ #pp-code-compiled,
21
+ #pp-footer,
22
+ #pp-preview-iframe {
23
+ contain: layout;
24
+ }
25
+
26
+ /* Header & Ribbon Structural Layout (Compiled) */
27
+ .qss-header-inner {
28
+ display: flex;
29
+ width: 100%;
30
+ height: 100%;
31
+ flex-direction: row;
32
+ align-items: center;
33
+ padding-left: 0.75rem;
34
+ padding-right: 0.75rem;
35
+ justify-content: space-between;
36
+ }
37
+
38
+ .qss-header-branding {
39
+ display: flex;
40
+ flex-direction: row;
41
+ align-items: center;
42
+ gap: 1.5rem;
43
+ width: var(--branding-w, 200px);
44
+ height: 100%;
45
+ }
46
+
47
+ .qss-header-tagline {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 0.125rem;
51
+ width: var(--tagline-w, 150px);
52
+ height: 100%;
53
+ }
54
+
55
+ .qss-header-ribbon {
56
+ flex: 1 1 0%;
57
+ display: flex;
58
+ flex-direction: row;
59
+ align-items: center;
60
+ justify-content: center;
61
+ gap: 2rem;
62
+ height: 100%;
63
+ }
64
+
65
+ .qss-ribbon-group {
66
+ display: flex;
67
+ flex-direction: column;
68
+ align-items: center;
69
+ gap: 0.125rem;
70
+ }
71
+
72
+ .qss-ribbon-activities {
73
+ display: flex;
74
+ flex-direction: row;
75
+ align-items: center;
76
+ gap: 0.25rem;
77
+ }
78
+
79
+ .qss-proficiency-filter-wrap {
80
+ display: flex;
81
+ flex-direction: column;
82
+ align-items: center;
83
+ gap: 0.125rem;
84
+ padding-left: 0.5rem;
85
+ padding-right: 0.5rem;
86
+ border-right: 1px solid rgba(30, 41, 59, 0.5);
87
+ height: 100%;
88
+ }
89
+
90
+ .qss-proficiency-filter-items {
91
+ display: flex;
92
+ flex-direction: row;
93
+ align-items: center;
94
+ gap: 0.375rem;
95
+ }
96
+
97
+ #pp-header-branding {
98
+ width: var(--branding-w, 200px);
99
+ height: 100%;
100
+ }
101
+
102
+ #pp-header-tagline {
103
+ width: var(--tagline-w, 150px);
104
+ height: 100%;
105
+ }
106
+
107
+ #pp-header-ribbon {
108
+ width: auto;
109
+ height: 100%;
110
+ }
111
+
112
+ /* Scrollbar Standard */
113
+ ::-webkit-scrollbar { width: 7px; height: 7px; }
114
+ ::-webkit-scrollbar-track { background: transparent; }
115
+ ::-webkit-scrollbar-thumb {
116
+ background: #334155;
117
+ border-radius: 100px;
118
+ border: 1.5px solid transparent;
119
+ background-clip: content-box;
120
+ }
121
+ ::-webkit-scrollbar-thumb:hover { background: #475569; background-clip: content-box; }
122
+
123
+ /* Icon Standardization */
124
+ .qss-icon-wrap {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ line-height: 1;
129
+ }
130
+
131
+ .qss-icon-raw,
132
+ .qss-icon-lucide {
133
+ width: 100%;
134
+ height: 100%;
135
+ display: block;
136
+ }
137
+
138
+ [class^="lucide-"], [class*=" lucide-"],
139
+ [class^="qss-icon-"], [class*=" qss-icon-"],
140
+ [class^="icon-"]:not(.icon-path), [class*=" icon-"]:not(.icon-path) {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ font-size: 14px;
145
+ width: 14px;
146
+ height: 14px;
147
+ }
148
+
149
+ /* Reactive QSS Properties */
150
+ .qss-mini-explorer-dropdown,
151
+ .qss-new-menu-dropdown,
152
+ .qss-link-menu-dropdown {
153
+ top: var(--dropdown-top, auto);
154
+ left: var(--dropdown-left, auto);
155
+ }
156
+
157
+ .qss-snippet-drawer {
158
+ width: var(--drawer-w, 0px);
159
+ }
160
+
161
+ .qss-fix-footer {
162
+ height: var(--footer-h, 0px);
163
+ }
164
+
165
+ /* File Editor Structural Identities */
166
+ #pp-file-editor-shell {
167
+ contain: layout;
168
+ }
169
+
170
+ #pp-editor-viewport {
171
+ display: flex;
172
+ gap: 1px;
173
+ padding: 1px;
174
+ background-color: #0b0d11;
175
+ contain: layout;
176
+ }
177
+
178
+ [id^="pp-editor-pane-"] {
179
+ contain: layout;
180
+ }
181
+
182
+ .no-scrollbar {
183
+ scrollbar-width: none;
184
+ -ms-overflow-style: none;
185
+ &::-webkit-scrollbar {
186
+ display: none;
187
+ }
188
+ }
189
+
190
+
191
+ .qss-pointer-events-none {
192
+ pointer-events: none;
193
+ }
194
+
195
+ .qss-pointer-events-auto {
196
+ pointer-events: auto;
197
+ }
198
+
199
+ /* Explicit hidden-state rule: the runtime engine is unreliable for toggling
200
+ display on this pre-migration element; this ID+class rule wins on
201
+ specificity without needing !important. */
202
+ #pp-preview-popup.qss {
203
+ display: flex;
204
+ flex-direction: column;
205
+ }
206
+
207
+ #pp-preview-popup.qss.hidden {
208
+ display: none;
209
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Sample QSS File
3
+ * Verifies the local transpiler loop.
4
+ */
5
+
6
+ #qss-test-object {
7
+ x: center;
8
+ y: 100;
9
+ w: 200;
10
+ h: 200;
11
+ bg: indigo-500;
12
+ depth: ui;
13
+ border-b: 2;
14
+ }
15
+
16
+ .qss-test-label {
17
+ x: 10;
18
+ y: 10;
19
+ text-size: 14;
20
+ text-color: slate-300;
21
+ font: bold;
22
+ }
@@ -0,0 +1,54 @@
1
+ require "ipaddr"
2
+
3
+ module Parapet
4
+ class ApplicationController < ActionController::Base
5
+ before_action :guard_request
6
+ before_action :set_current_session
7
+ before_action :require_authentication
8
+
9
+ helper Turbo::FramesHelper
10
+ helper Turbo::StreamsHelper
11
+ helper_method :current_user
12
+
13
+ def index
14
+ render "parapet/application"
15
+ end
16
+
17
+ private
18
+
19
+ def set_current_session
20
+ Current.session = Session.find_by(id: cookies.encrypted[:parapet_session_id])
21
+ end
22
+
23
+ def require_authentication
24
+ redirect_to login_path unless Current.user
25
+ end
26
+
27
+ def current_user
28
+ Current.user
29
+ end
30
+
31
+ def guard_request
32
+ environment = Rails.env.to_s
33
+ return head(:not_found) unless %w[development test].include?(environment)
34
+ return head(:not_found) unless loopback_request?
35
+ head(:not_found) unless expected_subdomain?
36
+ end
37
+
38
+ def loopback_request?
39
+ IPAddr.new(request.remote_ip).loopback?
40
+ rescue IPAddr::InvalidAddressError, ActionDispatch::RemoteIp::IpSpoofAttackError
41
+ false
42
+ end
43
+
44
+ # Defense in depth: the engine's own mount already constrains routing to this
45
+ # subdomain, so any request reaching this controller through normal routing is
46
+ # guaranteed to already satisfy this. This check exists for the cases that bypass
47
+ # normal routing -- a second, unconstrained mount point added by mistake, or direct
48
+ # invocation outside the router -- not for DNS-rebinding defense (the mount
49
+ # constraint and Host authorization handle that).
50
+ def expected_subdomain?
51
+ request.subdomain == Parapet.mount_subdomain
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ module Parapet
2
+ class BaseController < ApplicationController
3
+ include ApplicationHelper
4
+ helper ApplicationHelper
5
+
6
+ before_action :set_suite_context
7
+
8
+ protected
9
+
10
+ def set_suite_context
11
+ @explorer_type = 'project'
12
+ @root_path = Parapet::Engine.root.to_s
13
+ @scanner = Parapet::DirectoryScanner.new(@root_path)
14
+ physical_tree = @scanner.scan
15
+ @tree_data = Parapet::ExplorerManager.virtualize(physical_tree, @explorer_type)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Parapet
2
+ module BuildSuite
3
+ class DashboardController < Parapet::BaseController
4
+ def index
5
+ @active_suite = 'build'
6
+ render "parapet/dashboards/index"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,95 @@
1
+ module Parapet
2
+ module CssSuite
3
+ class CompilerController < ApplicationController
4
+ def grammar
5
+ registry_path = Parapet::Engine.root.join("app/assets/config/parapet/grammar-registry.json")
6
+ if File.exist?(registry_path)
7
+ render json: JSON.parse(File.read(registry_path))
8
+ else
9
+ render json: { error: "Grammar registry not found" }, status: :not_found
10
+ end
11
+ end
12
+
13
+ # Bypasses Propshaft/Sprockets to serve the absolute latest generated core
14
+ def live
15
+ scss_path = File.join(QSS.stylesheets_path, "qss", "qss_core.scss")
16
+
17
+ if File.exist?(scss_path)
18
+ begin
19
+ result = Sass.compile_string(
20
+ "@import \"parapet/qss_variables\";\n@import \"qss/qss_core\";\n", # Parapet's settings, then core (as in qss_application.scss)
21
+ load_paths: [ Parapet::Engine.root.join("app/assets/stylesheets").to_s, QSS.stylesheets_path ],
22
+ syntax: :scss,
23
+ style: :expanded
24
+ )
25
+ render plain: result.css, content_type: 'text/css'
26
+ rescue => e
27
+ logger.error "SCSS Compilation Error: #{e.message}\n#{e.backtrace.join("\n")}"
28
+ render plain: "/* SCSS Compilation Error: #{e.message} */", content_type: 'text/css', status: :internal_server_error
29
+ end
30
+ else
31
+ render plain: "/* qss_core.scss not found at #{scss_path} */", content_type: 'text/css', status: :not_found
32
+ end
33
+ end
34
+
35
+ def compile
36
+ scss_content = params[:scss]
37
+
38
+ if scss_content.blank?
39
+ render json: { css: "", error: nil }
40
+ return
41
+ end
42
+
43
+ begin
44
+ # Compile using sass-embedded
45
+ # We include the engine's stylesheet path for @imports
46
+ load_paths = [ Parapet::Engine.root.join("app/assets/stylesheets").to_s ]
47
+
48
+ result = Sass.compile_string(
49
+ scss_content,
50
+ load_paths: load_paths,
51
+ syntax: :scss,
52
+ style: :expanded
53
+ )
54
+
55
+ render json: { css: result.css, error: nil }
56
+ rescue Sass::CompileError => e
57
+ render json: {
58
+ css: nil,
59
+ error: e.message,
60
+ line: e.span&.start&.line,
61
+ column: e.span&.start&.column
62
+ }, status: :unprocessable_entity
63
+ rescue => e
64
+ render json: { css: nil, error: "System Error: #{e.message}" }, status: :internal_server_error
65
+ end
66
+ end
67
+
68
+ def flush
69
+ path = params[:path]
70
+ content = params[:content]
71
+
72
+ if path.blank? || content.nil?
73
+ render json: { error: "Path and content are required" }, status: :bad_request
74
+ return
75
+ end
76
+
77
+ full_path = File.expand_path(Rails.root.join(path).to_s)
78
+ root_path = File.expand_path(Rails.root.to_s)
79
+ root_prefix = root_path.end_with?(File::SEPARATOR) ? root_path : "#{root_path}#{File::SEPARATOR}"
80
+
81
+ unless full_path == root_path || full_path.start_with?(root_prefix)
82
+ render json: { error: "Access Denied" }, status: :forbidden
83
+ return
84
+ end
85
+
86
+ begin
87
+ File.write(full_path, content)
88
+ render json: { success: true, path: path }
89
+ rescue => e
90
+ render json: { error: "Failed to write file: #{e.message}" }, status: :internal_server_error
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ module Parapet
2
+ module CssSuite
3
+ class DashboardController < Parapet::BaseController
4
+ def index
5
+ @active_suite = 'css'
6
+ render "parapet/dashboards/index"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ module Parapet
2
+ module CssSuite
3
+ class PreviewController < ApplicationController
4
+ layout false
5
+
6
+ def show
7
+ path = params[:path].to_s
8
+ Rails.logger.info "Styles Workbench: Preview Request for #{path}"
9
+
10
+ if path.blank? || !path.end_with?('.erb')
11
+ Rails.logger.warn "Styles Workbench: Invalid preview path: #{path}"
12
+ render plain: "Select an ERB view file to preview.", status: :bad_request
13
+ return
14
+ end
15
+
16
+ # Security & Path Resolution
17
+ root_path = File.expand_path(Parapet::Engine.root.to_s)
18
+ full_path = File.expand_path(File.join(root_path, path.sub(/^\//, '')))
19
+
20
+ unless full_path.start_with?(root_path) && File.exist?(full_path)
21
+ render plain: "File not found or access denied.", status: :not_found
22
+ return
23
+ end
24
+
25
+ begin
26
+ # We attempt to render the file as a partial.
27
+ prepend_view_path Parapet::Engine.root.join("app/views")
28
+
29
+ # app/views/parapet/shared/_header.html.erb -> parapet/shared/header
30
+ clean_path = path.sub(%r{^.*app/views/}, '').sub(/\.html\.erb$/, '')
31
+
32
+ # Remove leading underscore from the final segment (the filename)
33
+ parts = clean_path.split('/')
34
+ parts[-1] = parts[-1].sub(/^_/, '')
35
+ partial_path = parts.join('/')
36
+
37
+ render partial: partial_path
38
+ rescue => e
39
+ render html: "<div class='qss-p[20] qss-bg-red-500/10 qss-border qss-border-red-500/20 qss-text-red-400 qss-text-size[11]'>
40
+ <strong>Preview Error:</strong><br/>#{e.message}
41
+ </div>".html_safe
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,8 @@
1
+ module Parapet
2
+ class DashboardController < Parapet::BaseController
3
+ def index
4
+ @active_suite = params[:suite] || 'project'
5
+ render "parapet/dashboards/index"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ module Parapet
2
+ class DraftsController < ApplicationController
3
+ def show
4
+ draft = DraftRecoveryStore.find(user: current_user, path: file_path)
5
+ return head :no_content unless draft
6
+
7
+ render json: { content: draft.content }
8
+ end
9
+
10
+ def update
11
+ DraftRecoveryStore.save!(
12
+ user: current_user,
13
+ path: file_path,
14
+ content: params[:content].to_s
15
+ )
16
+ head :no_content
17
+ end
18
+
19
+ def destroy
20
+ DraftRecoveryStore.clear!(user: current_user, path: file_path)
21
+ head :no_content
22
+ end
23
+
24
+ private
25
+
26
+ def file_path
27
+ params[:path].to_s.sub(/^\//, "")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ module Parapet
2
+ class ExplorersController < ApplicationController
3
+ include ApplicationHelper
4
+
5
+ def show
6
+ @explorer_type = params[:type] || 'project'
7
+ @perspective = params[:perspective] || 'project'
8
+
9
+ # Always scan from the engine root to allow cross-directory virtualization (like Registry)
10
+ @root_path = Parapet::Engine.root.to_s
11
+
12
+ # Initialize scanner and generate tree
13
+ @scanner = Parapet::DirectoryScanner.new(@root_path)
14
+ physical_tree = @scanner.scan
15
+
16
+ # Transform the physical tree into virtual perspectives
17
+ @tree_data = Parapet::ExplorerManager.virtualize(physical_tree, @explorer_type, perspective: @perspective)
18
+
19
+ # Determine which partial to render
20
+ mapped_type = case @explorer_type
21
+ when 'styles' then 'css'
22
+ when 'ideas' then 'intentions'
23
+ else @explorer_type
24
+ end
25
+
26
+ explorer_partial = "parapet/shared/explorers/#{mapped_type}"
27
+ unless lookup_context.exists?(explorer_partial, [], true)
28
+ explorer_partial = "parapet/shared/explorers/base"
29
+ end
30
+
31
+ respond_to do |format|
32
+ format.turbo_stream do
33
+ if turbo_frame_request?
34
+ render partial: explorer_partial, locals: { type: @explorer_type, tree_data: @tree_data }
35
+ else
36
+ render turbo_stream: [
37
+ turbo_stream.replace("qss-perspective-filters", partial: "parapet/activities/perspective_filters", locals: { explorer_type: @explorer_type }),
38
+ turbo_stream.replace("qss-explorer-content", partial: explorer_partial, locals: { type: @explorer_type, tree_data: @tree_data }),
39
+ turbo_stream.replace("qss-progress-area", partial: "parapet/activities/progress_area", locals: { summary: Parapet::ExplorerManager.summary(@explorer_type), root_path: @root_path })
40
+ ]
41
+ end
42
+ end
43
+ format.html do
44
+ if turbo_frame_request?
45
+ render partial: explorer_partial, locals: { type: @explorer_type, tree_data: @tree_data }
46
+ else
47
+ render partial: "parapet/activities/explorer_tree", locals: { tree_data: @tree_data }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end