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,251 @@
1
+ (function() {
2
+ class SnippetLibraryCard extends ActivityCard {
3
+ get cardTitle() { return 'Snippet Library'; }
4
+ get cardKeyArea() { return 'Studio'; }
5
+ get cardProficiency() { return 2; }
6
+ get cardIcon() {
7
+ return `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
8
+ <path class="qss-icon-path" d="M16 6V18M12 6V18M8 6V18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
9
+ <path class="qss-icon-path" d="M20 3H4C2.89543 3 2 3.89543 2 5V19C2 20.1046 2.89543 21 4 21H20C21.1046 21 22 20.1046 22 19V5C22 3.89543 21.1046 3 20 3Z" stroke="currentColor" stroke-width="2"/>
10
+ </svg>`;
11
+ }
12
+
13
+ defaults() {
14
+ return Object.assign({}, super.defaults(), {
15
+ width: 320, height: 450,
16
+ minWidth: 250, minHeight: 300
17
+ });
18
+ }
19
+
20
+ getSnippets() {
21
+ return [
22
+ { category: 'RSpec Assertions', items: [
23
+ {
24
+ label: 'Equality',
25
+ code: 'expect(actual).to eq(expected)',
26
+ description: 'Asserts that actual value equals expected.',
27
+ example: 'expect(user.name).to eq("John")'
28
+ },
29
+ {
30
+ label: 'Be Truthy',
31
+ code: 'expect(result).to be_truthy',
32
+ description: 'Asserts that the value is not nil or false.',
33
+ example: 'expect(user.save).to be_truthy'
34
+ },
35
+ {
36
+ label: 'Include',
37
+ code: 'expect(array).to include(item)',
38
+ description: 'Checks if a collection contains an element.',
39
+ example: 'expect(roles).to include("admin")'
40
+ },
41
+ {
42
+ label: 'Change',
43
+ code: 'expect { action }.to change(Model, :count).by(1)',
44
+ description: 'Asserts that a block changes a value.',
45
+ example: 'expect { post :create }.to change(User, :count).by(1)'
46
+ },
47
+ {
48
+ label: 'Raise Error',
49
+ code: 'expect { action }.to raise_error(ErrorClass)',
50
+ description: 'Asserts that a block raises a specific error.',
51
+ example: 'expect { user.save! }.to raise_error(ActiveRecord::RecordInvalid)'
52
+ }
53
+ ]},
54
+ { category: 'Minitest Assertions', items: [
55
+ {
56
+ label: 'Assert Equal',
57
+ code: 'assert_equal expected, actual',
58
+ description: 'Standard equality assertion.',
59
+ example: 'assert_equal 4, 2 + 2'
60
+ },
61
+ {
62
+ label: 'Assert Result',
63
+ code: 'assert result',
64
+ description: 'Asserts that the result is truthy.',
65
+ example: 'assert @user.valid?'
66
+ },
67
+ {
68
+ label: 'Assert Difference',
69
+ code: 'assert_difference "Model.count", 1 do\n action\nend',
70
+ description: 'Checks for change in count.',
71
+ example: 'assert_difference "Post.count" do\n create_post\nend'
72
+ },
73
+ {
74
+ label: 'Assert Raises',
75
+ code: 'assert_raises(ErrorClass) { action }',
76
+ description: 'Asserts that a block raises an error.',
77
+ example: 'assert_raises(ArgumentError) { my_method(nil) }'
78
+ }
79
+ ]},
80
+ { category: 'Setup & Mocks', items: [
81
+ {
82
+ label: 'RSpec Let',
83
+ code: 'let(:user) { create(:user) }',
84
+ description: 'Lazy-evaluated helper method.',
85
+ example: 'let(:admin) { create(:user, role: "admin") }'
86
+ },
87
+ {
88
+ label: 'RSpec Before',
89
+ code: 'before do\n # setup\nend',
90
+ description: 'Runs before each test.',
91
+ example: 'before { sign_in user }'
92
+ },
93
+ {
94
+ label: 'RSpec Allow',
95
+ code: 'allow(obj).to receive(:msg).and_return(value)',
96
+ description: 'Stub a method call.',
97
+ example: 'allow(PaymentGateway).to receive(:process).and_return(true)'
98
+ },
99
+ {
100
+ label: 'RSpec Expect Receive',
101
+ code: 'expect(obj).to receive(:msg)',
102
+ description: 'Mock expectation.',
103
+ example: 'expect(notifier).to receive(:send_email)'
104
+ }
105
+ ]},
106
+ { category: 'FactoryBot', items: [
107
+ {
108
+ label: 'Create',
109
+ code: 'user = create(:user)',
110
+ description: 'Save instance to database.',
111
+ example: '@user = create(:user, email: "test@example.com")'
112
+ },
113
+ {
114
+ label: 'Build',
115
+ code: 'user = build(:user)',
116
+ description: 'Instantiate without saving.',
117
+ example: 'user = build(:user, name: nil)'
118
+ },
119
+ {
120
+ label: 'Build Stubbed',
121
+ code: 'user = build_stubbed(:user)',
122
+ description: 'Fast mock instance.',
123
+ example: 'user = build_stubbed(:user)'
124
+ },
125
+ {
126
+ label: 'Attributes For',
127
+ code: 'attrs = attributes_for(:user)',
128
+ description: 'Get hash of attributes.',
129
+ example: 'post :create, params: { user: attributes_for(:user) }'
130
+ }
131
+ ]},
132
+ { category: 'Cucumber/Gherkin', items: [
133
+ {
134
+ label: 'Feature',
135
+ code: 'Feature: Some description\n\n Scenario: Some context\n Given some precondition\n When some action\n Then some result',
136
+ description: 'Basic feature structure.'
137
+ },
138
+ {
139
+ label: 'Background',
140
+ code: 'Background:\n Given the following users exist:\n | name | email |\n | John | john@example.com |',
141
+ description: 'Runs before each scenario.'
142
+ },
143
+ {
144
+ label: 'Scenario Outline',
145
+ code: 'Scenario Outline: feeding a <animal>\n Given the <animal> is hungry\n When I feed it <food>\n Then it should be <state>\n\n Examples:\n | animal | food | state |\n | cat | fish | happy |\n | dog | bone | happy |',
146
+ description: 'Data-driven testing.'
147
+ }
148
+ ]}
149
+ ];
150
+ }
151
+
152
+ buildContent() {
153
+ return `
154
+ <div class="qss-snippet-library-container qss-flex qss-flex-col qss-h-full qss-bg-[#111418] qss-text-[11px] qss-overflow-hidden">
155
+ <!-- Search Bar -->
156
+ <div class="qss-px-2 qss-py-2 qss-border-b qss-border-slate-800 qss-bg-[#16191e]/50">
157
+ <div class="qss-relative">
158
+ <input type="text" class="qss-snippet-search qss-w-full qss-bg-black/40 qss-border qss-border-slate-700 qss-rounded qss-px-7 qss-py-1 qss-text-slate-300 placeholder:qss-text-slate-600 focus:qss-outline-none focus:qss-border-indigo-500/50 qss-transition-colors" placeholder="Search snippets...">
159
+ <i class="qss-icon-search qss-absolute qss-left-2 qss-top-1/2 qss--translate-y-1/2 qss-text-slate-600" style="font-size: 12px;"></i>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Snippet List -->
164
+ <div class="qss-snippet-list qss-flex-1 qss-overflow-y-auto qss-custom-scrollbar qss-p-2 qss-space-y-4">
165
+ ${this.renderSnippets()}
166
+ </div>
167
+
168
+ <div class="qss-px-2 qss-py-1.5 qss-border-t qss-border-slate-800 qss-bg-black/20 qss-text-[9px] qss-text-slate-500 qss-italic">
169
+ Click snippet to insert at cursor
170
+ </div>
171
+ </div>
172
+ `;
173
+ }
174
+
175
+ renderSnippets(filter = '') {
176
+ const snippets = this.getSnippets();
177
+ const search = filter.toLowerCase();
178
+
179
+ return snippets.map(cat => {
180
+ const filteredItems = cat.items.filter(i =>
181
+ i.label.toLowerCase().includes(search) ||
182
+ i.code.toLowerCase().includes(search)
183
+ );
184
+
185
+ if (filteredItems.length === 0) return '';
186
+
187
+ return `
188
+ <div class="qss-snippet-category">
189
+ <div class="qss-text-[10px] qss-font-bold qss-text-slate-500 qss-uppercase qss-tracking-widest qss-mb-2 qss-px-1 qss-mt-2">${cat.category}</div>
190
+ <div class="qss-space-y-2">
191
+ ${filteredItems.map(item => `
192
+ <div class="qss-snippet-item qss-group qss-cursor-pointer qss-bg-white/5 qss-border qss-border-white/5 hover:qss-border-indigo-500/30 qss-rounded qss-overflow-hidden qss-transition-all" data-code="${item.code.replace(/"/g, '&quot;')}">
193
+ <div class="qss-p-2">
194
+ <div class="qss-flex qss-items-center qss-justify-between qss-mb-1">
195
+ <span class="qss-text-[11px] qss-text-slate-300 group-hover:qss-text-indigo-300 qss-font-medium qss-transition-colors">${item.label}</span>
196
+ <i class="qss-icon-plus qss-text-slate-600 group-hover:qss-text-indigo-400 qss-transition-colors" style="font-size: 11px;"></i>
197
+ </div>
198
+ <div class="qss-text-[10px] qss-text-slate-500 qss-font-mono qss-italic qss-mb-2">
199
+ ${item.description || item.code.split('\n')[0]}
200
+ </div>
201
+ <div class="qss-bg-black/40 qss-rounded qss-p-1.5 qss-font-mono qss-text-[10px] qss-text-slate-400 group-hover:qss-text-slate-300 qss-overflow-x-hidden">
202
+ ${item.code.split('\n')[0]}
203
+ </div>
204
+ </div>
205
+ ${item.example ? `
206
+ <div class="qss-px-2 qss-pb-2 qss-pt-1 qss-border-t qss-border-white/5 qss-bg-black/10">
207
+ <div class="qss-text-[8px] qss-text-slate-700 qss-uppercase qss-font-bold qss-mb-1">Example:</div>
208
+ <pre class="qss-text-[9px] qss-text-indigo-500/60 qss-font-mono qss-leading-tight qss-whitespace-pre-wrap">${item.example}</pre>
209
+ </div>
210
+ ` : ''}
211
+ </div>
212
+ `).join('')}
213
+ </div>
214
+ </div>
215
+ `;
216
+ }).join('');
217
+ }
218
+
219
+ onInit() {
220
+ const searchInput = this.element.querySelector('.qss-snippet-search');
221
+ const listContainer = this.element.querySelector('.qss-snippet-list');
222
+
223
+ searchInput.addEventListener('input', (e) => {
224
+ listContainer.innerHTML = this.renderSnippets(e.target.value);
225
+ });
226
+
227
+ this.element.addEventListener('click', (e) => {
228
+ const item = e.target.closest('.qss-snippet-item');
229
+ if (!item) return;
230
+
231
+ const code = item.dataset.code;
232
+ if (!code) return;
233
+
234
+ // Dispatch event to insert into active editor
235
+ document.dispatchEvent(new CustomEvent('parapet:insertSnippet', {
236
+ detail: { code: code }
237
+ }));
238
+
239
+ // Visual feedback
240
+ const icon = item.querySelector('.qss-icon-plus');
241
+ if (icon) {
242
+ icon.className = 'qss-icon-check text-emerald-400';
243
+ setTimeout(() => icon.className = 'qss-icon-plus text-slate-700', 1000);
244
+ }
245
+ });
246
+ }
247
+ }
248
+
249
+ SnippetLibraryCard.cardId = 'snippet-library';
250
+ ActivityCards.registry['snippet-library'] = SnippetLibraryCard;
251
+ })();
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Parapet Explorer Resizer
3
+ * Handles dynamic resizing of the explorer bar with persistence.
4
+ */
5
+ (function() {
6
+ let isResizing = false;
7
+ let explorer = null;
8
+ let resizer = null;
9
+ const MIN_WIDTH = 130;
10
+ const MAX_WIDTH = 400;
11
+ const STORAGE_KEY = 'parapet-explorer-width';
12
+
13
+ function init() {
14
+ explorer = document.getElementById('pp-explorer-bar');
15
+ resizer = document.getElementById('pp-explorer-resizer');
16
+ if (!explorer || !resizer) return;
17
+
18
+ // Restore persisted width
19
+ const savedWidth = localStorage.getItem(STORAGE_KEY);
20
+ if (savedWidth) {
21
+ updateWidth(parseFloat(savedWidth));
22
+ }
23
+
24
+ resizer.addEventListener('mousedown', startResize);
25
+ window.addEventListener('mousemove', resize);
26
+ window.addEventListener('mouseup', stopResize);
27
+ }
28
+
29
+ function startResize(e) {
30
+ isResizing = true;
31
+ document.body.style.cursor = 'col-resize';
32
+ // Prevent text selection during drag
33
+ document.body.style.userSelect = 'none';
34
+ e.preventDefault();
35
+ }
36
+
37
+ function resize(e) {
38
+ if (!isResizing) return;
39
+
40
+ const canvas = document.getElementById('qss-canvas');
41
+ if (!canvas) return;
42
+
43
+ const canvasRect = canvas.getBoundingClientRect();
44
+ const unit = parseFloat(getComputedStyle(canvas.firstElementChild || canvas).getPropertyValue('--qnt')) || 1; // the canvas unit lives on its children
45
+
46
+ // Calculate new width in Quants (relative to Global Nav at x=24)
47
+ let newWidth = (e.clientX - canvasRect.left) / unit - 24;
48
+
49
+ // Apply Constraints
50
+ newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, newWidth));
51
+
52
+ updateWidth(newWidth);
53
+ }
54
+
55
+ function stopResize() {
56
+ if (!isResizing) return;
57
+ isResizing = false;
58
+ document.body.style.cursor = '';
59
+ document.body.style.userSelect = '';
60
+
61
+ // Persist final width
62
+ const width = document.documentElement.style.getPropertyValue('--explorer-bar-w').replace('px', '');
63
+ if (width) {
64
+ localStorage.setItem(STORAGE_KEY, width);
65
+ }
66
+ }
67
+
68
+ function updateWidth(width) {
69
+ // Update CSS variable directly for Thin Wrapper anchoring
70
+ document.documentElement.style.setProperty('--explorer-bar-w', `${width}px`);
71
+
72
+ // Also update legacy class for JIT compatibility if needed
73
+ const classes = Array.from(explorer.classList);
74
+ const oldClass = classes.find(c => c.startsWith('w[') && !c.includes('%'));
75
+
76
+ if (oldClass) explorer.classList.remove(oldClass);
77
+
78
+ const newClass = `w[${Math.round(width)}]`;
79
+ // explorer.classList.add(newClass); // Disabling JIT class in favor of var()
80
+
81
+ // Maintain 3:4 aspect ratio for Widget Area
82
+ const widgetArea = document.getElementById('pp-explorer-widgets');
83
+ if (widgetArea) {
84
+ const newHeight = Math.round(width * 1.33); // 4/3 aspect ratio
85
+ document.documentElement.style.setProperty('--explorer-widgets-h', `${newHeight}px`);
86
+ }
87
+ }
88
+
89
+ // Initialize on load and turbo:load
90
+ document.addEventListener('DOMContentLoaded', init);
91
+ document.addEventListener('turbo:load', init);
92
+ })();
@@ -0,0 +1,342 @@
1
+ /*
2
+ * Parapet-side QSSEngine extensions (QSS item C, 2026-09-23).
3
+ * Moved out of qss-engine.js so the core engine carries no Parapet DOM ids or events.
4
+ * Loaded (defer) immediately after qss-engine.js; attaches to the shared QSSEngine
5
+ * object, so existing callers (QSSEngine.syncRoot(), onclick="QSSEngine.revealPhantom(..)")
6
+ * are unchanged. Not part of the QSS core / gem.
7
+ */
8
+ (function () {
9
+ // Object definitions (Button, Toggle, Field, Dropdown, Card): layer-2 material, registered
10
+ // here through the core's registerObject() mechanism. Reference copy in
11
+ // archive/qss_parapet/layer2_holding/qss-objects.json.
12
+ const OBJECT_DEFINITIONS = {
13
+ "Button": {
14
+ "name": "Button",
15
+ "params": [
16
+ "w",
17
+ "h"
18
+ ],
19
+ "intrinsic": {
20
+ "w": 120,
21
+ "h": 44
22
+ },
23
+ "adjectives": [
24
+ "bg-indigo-500",
25
+ "rounded[4]",
26
+ "flex",
27
+ "items-center",
28
+ "justify-center",
29
+ "clickable",
30
+ "smooth"
31
+ ],
32
+ "slots": [
33
+ {
34
+ "name": "label",
35
+ "pos": [
36
+ "center",
37
+ "center"
38
+ ],
39
+ "class": "text-white weight[bold] text-size[14]"
40
+ }
41
+ ]
42
+ },
43
+ "Toggle": {
44
+ "name": "Toggle",
45
+ "params": [
46
+ "w",
47
+ "h"
48
+ ],
49
+ "intrinsic": {
50
+ "w": 40,
51
+ "h": 20
52
+ },
53
+ "adjectives": [
54
+ "bg-slate-800",
55
+ "rounded[10]",
56
+ "clickable",
57
+ "smooth"
58
+ ],
59
+ "slots": [
60
+ {
61
+ "name": "knob",
62
+ "pos": [
63
+ 2,
64
+ 2
65
+ ],
66
+ "intrinsic": {
67
+ "w": 16,
68
+ "h": 16
69
+ },
70
+ "class": "bg-white rounded[8] smooth"
71
+ }
72
+ ]
73
+ },
74
+ "Field": {
75
+ "name": "Field",
76
+ "params": [
77
+ "w",
78
+ "h"
79
+ ],
80
+ "intrinsic": {
81
+ "w": 300,
82
+ "h": 64
83
+ },
84
+ "adjectives": [
85
+ "flex",
86
+ "flex-col",
87
+ "gap[4]"
88
+ ],
89
+ "slots": [
90
+ {
91
+ "name": "label",
92
+ "pos": [
93
+ 0,
94
+ 0
95
+ ],
96
+ "class": "text-slate-400 text-size[12] weight[semibold]"
97
+ },
98
+ {
99
+ "name": "input",
100
+ "pos": [
101
+ 0,
102
+ 20
103
+ ],
104
+ "intrinsic": {
105
+ "w": "inherit",
106
+ "h": 40
107
+ },
108
+ "class": "bg-slate-900 border[1] border-slate-800 rounded[4] px[12] text-white text-size[14]"
109
+ }
110
+ ]
111
+ },
112
+ "Dropdown": {
113
+ "name": "Dropdown",
114
+ "params": [
115
+ "w",
116
+ "h",
117
+ "row_height"
118
+ ],
119
+ "intrinsic": {
120
+ "w": 200,
121
+ "h": 40
122
+ },
123
+ "variables": {
124
+ "row_height": 32,
125
+ "max_rows": 5
126
+ },
127
+ "slots": [
128
+ {
129
+ "name": "header",
130
+ "pos": [
131
+ 0,
132
+ 0
133
+ ],
134
+ "intrinsic": {
135
+ "w": "inherit",
136
+ "h": "inherit"
137
+ },
138
+ "class": "bg-slate-800 border[1] border-slate-700 rounded[4] px[12] flex items-center justify-between clickable"
139
+ },
140
+ {
141
+ "name": "list",
142
+ "pos": [
143
+ 0,
144
+ 42
145
+ ],
146
+ "intrinsic": {
147
+ "w": "inherit",
148
+ "h": "computed"
149
+ },
150
+ "class": "depth[overlay] bg-brand-main border[1] border-slate-800 rounded[4] scroll[y] hidden"
151
+ }
152
+ ]
153
+ },
154
+ "Card": {
155
+ "name": "Card",
156
+ "params": [
157
+ "w",
158
+ "h",
159
+ "bg"
160
+ ],
161
+ "intrinsic": {
162
+ "w": 400,
163
+ "h": 300
164
+ },
165
+ "adjectives": [
166
+ "bg-slate-900",
167
+ "border[1]",
168
+ "border-slate-800",
169
+ "rounded[8]",
170
+ "scroll[hidden]"
171
+ ]
172
+ }
173
+ };
174
+ Object.entries(OBJECT_DEFINITIONS).forEach(([name, definition]) => QSSEngine.registerObject(name, definition));
175
+
176
+ function setRoot(path, event) {
177
+ if (event) event.stopPropagation();
178
+ const suite = document.querySelector('#pp-explorer-header span')?.textContent?.trim() || 'default';
179
+ localStorage.setItem(`qss-root-${suite}`, path);
180
+
181
+ const label = document.getElementById('pp-current-root');
182
+ if (label) {
183
+ label.textContent = path === '/' ? 'Root' : path.split('/').filter(p => p).pop();
184
+ }
185
+
186
+ const dropdown = document.getElementById('pp-root-dropdown');
187
+ if (dropdown) dropdown.classList.add('hidden');
188
+
189
+ // Trigger reload of explorer content
190
+ const explorerContent = document.getElementById('qss-explorer-content');
191
+ if (explorerContent) {
192
+ const url = new URL(explorerContent.src, window.location.origin);
193
+ url.searchParams.set('root', path);
194
+ explorerContent.src = url.toString();
195
+ }
196
+ }
197
+
198
+ function syncRoot() {
199
+ const suite = document.querySelector('#pp-explorer-header span')?.textContent?.trim() || 'default';
200
+ const currentRoot = localStorage.getItem(`qss-root-${suite}`) || '/';
201
+ const label = document.getElementById('pp-current-root');
202
+ if (label) {
203
+ label.textContent = currentRoot === '/' ? 'Root' : currentRoot.split('/').filter(p => p).pop();
204
+ }
205
+
206
+ // Ensure explorer frame knows the root
207
+ const explorerContent = document.getElementById('qss-explorer-content');
208
+ if (explorerContent && explorerContent.src) {
209
+ try {
210
+ const url = new URL(explorerContent.src, window.location.origin);
211
+ if (!url.searchParams.has('root')) {
212
+ url.searchParams.set('root', currentRoot);
213
+ explorerContent.src = url.toString();
214
+ }
215
+ } catch (e) {
216
+ console.warn('Invalid explorer src', e);
217
+ }
218
+ }
219
+ }
220
+
221
+ function setPerspective(type, event) {
222
+ if (event) event.stopPropagation();
223
+ const suite = document.querySelector('#pp-explorer-header span')?.textContent?.trim() || 'default';
224
+ localStorage.setItem(`qss-perspective-${suite}`, type);
225
+
226
+ // Update UI state
227
+ document.querySelectorAll('.perspective-btn').forEach(btn => {
228
+ const icon = btn.querySelector('i');
229
+ if (btn.dataset.perspective === type) {
230
+ btn.classList.add('bg-white/10');
231
+ if (icon) {
232
+ icon.classList.add('text-indigo-400');
233
+ icon.classList.remove('text-slate-500');
234
+ }
235
+ } else {
236
+ btn.classList.remove('bg-white/10');
237
+ if (icon) {
238
+ icon.classList.remove('text-indigo-400');
239
+ icon.classList.add('text-slate-500');
240
+ }
241
+ }
242
+ });
243
+
244
+ // Trigger reload of explorer content
245
+ const explorerContent = document.getElementById('qss-explorer-content');
246
+ if (explorerContent) {
247
+ const url = new URL(explorerContent.src, window.location.origin);
248
+ url.searchParams.set('perspective', type);
249
+ explorerContent.src = url.toString();
250
+ }
251
+ }
252
+
253
+ function syncPerspective() {
254
+ const suite = document.querySelector('#pp-explorer-header span')?.textContent?.trim() || 'default';
255
+ const type = localStorage.getItem(`qss-perspective-${suite}`) || 'project';
256
+
257
+ // Update UI state
258
+ document.querySelectorAll('.perspective-btn').forEach(btn => {
259
+ const icon = btn.querySelector('i');
260
+ if (btn.dataset.perspective === type) {
261
+ btn.classList.add('bg-white/10');
262
+ if (icon) {
263
+ icon.classList.add('text-indigo-400');
264
+ icon.classList.remove('text-slate-500');
265
+ }
266
+ } else {
267
+ btn.classList.remove('bg-white/10');
268
+ if (icon) {
269
+ icon.classList.remove('text-indigo-400');
270
+ icon.classList.add('text-slate-500');
271
+ }
272
+ }
273
+ });
274
+
275
+ // Ensure explorer frame knows the perspective
276
+ const explorerContent = document.getElementById('qss-explorer-content');
277
+ if (explorerContent && explorerContent.src) {
278
+ try {
279
+ const url = new URL(explorerContent.src, window.location.origin);
280
+ if (!url.searchParams.has('perspective')) {
281
+ url.searchParams.set('perspective', type);
282
+ explorerContent.src = url.toString();
283
+ }
284
+ } catch (e) {
285
+ console.warn('Invalid explorer src', e);
286
+ }
287
+ }
288
+ }
289
+
290
+ function revealPhantom(nounId, event) {
291
+ if (event) event.stopPropagation();
292
+ const el = document.getElementById(nounId);
293
+ if (el) {
294
+ el.classList.toggle('qss-phantom-revealed');
295
+
296
+ // Highlight the row in the explorer if we have the event
297
+ if (event && event.currentTarget) {
298
+ const row = event.currentTarget.closest('.qss-tree-row');
299
+ if (row) {
300
+ if (el.classList.contains('qss-phantom-revealed')) {
301
+ row.classList.add('bg-indigo-500/10');
302
+ } else {
303
+ row.classList.remove('bg-indigo-500/10');
304
+ }
305
+ }
306
+ }
307
+ } else {
308
+ console.warn(`Phantom Noun #${nounId} not found in DOM`);
309
+ }
310
+ }
311
+
312
+ function startVisualSyncing() {
313
+ window.addEventListener('parapet:objectSelected', (e) => {
314
+ const nounId = e.detail.id;
315
+ const treeRows = document.querySelectorAll('#qss-tree-container .qss-tree-row');
316
+
317
+ treeRows.forEach(row => {
318
+ const nameEl = row.querySelector('.qss-tree-row-name');
319
+ const nodeName = nameEl?.textContent?.trim();
320
+
321
+ if (nodeName === nounId || nodeName === `#${nounId}`) {
322
+ // Highlight selected node
323
+ document.querySelectorAll('.qss-tree-row.selected-sync').forEach(r => {
324
+ r.classList.remove('selected-sync', 'bg-indigo-500/20', 'ring-1', 'ring-indigo-500/50');
325
+ });
326
+
327
+ row.classList.add('selected-sync', 'bg-indigo-500/20', 'ring-1', 'ring-indigo-500/50');
328
+ row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
329
+ }
330
+ });
331
+ });
332
+ }
333
+
334
+ Object.assign(QSSEngine, {
335
+ setRoot,
336
+ syncRoot,
337
+ setPerspective,
338
+ syncPerspective,
339
+ revealPhantom,
340
+ startVisualSyncing
341
+ });
342
+ })();