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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +48 -0
- data/Rakefile +7 -0
- data/app/assets/config/parapet/grammar-registry.json +52 -0
- data/app/assets/images/parapet/icons/activities/component-inspector.svg +4 -0
- data/app/assets/images/parapet/icons/activities/coverage-heatmap.svg +3 -0
- data/app/assets/images/parapet/icons/activities/discovery-sidebar.svg +3 -0
- data/app/assets/images/parapet/icons/activities/environment-toggles.svg +4 -0
- data/app/assets/images/parapet/icons/activities/failure-inspector.svg +5 -0
- data/app/assets/images/parapet/icons/activities/flaky-history.svg +3 -0
- data/app/assets/images/parapet/icons/activities/flight-recorder.svg +5 -0
- data/app/assets/images/parapet/icons/activities/health-graph.svg +3 -0
- data/app/assets/images/parapet/icons/activities/injection-engine.svg +3 -0
- data/app/assets/images/parapet/icons/activities/live-sql-stream.svg +5 -0
- data/app/assets/images/parapet/icons/activities/live-web-preview.svg +4 -0
- data/app/assets/images/parapet/icons/activities/monaco-editor.svg +5 -0
- data/app/assets/images/parapet/icons/activities/progress-area.svg +3 -0
- data/app/assets/images/parapet/icons/activities/registry-metadata.svg +5 -0
- data/app/assets/images/parapet/icons/activities/seed-inspector.svg +4 -0
- data/app/assets/images/parapet/icons/activities/slow-test-bottlenecks.svg +4 -0
- data/app/assets/images/parapet/icons/activities/smart-search.svg +4 -0
- data/app/assets/images/parapet/icons/activities/snippet-library.svg +4 -0
- data/app/assets/images/parapet/icons/activities/terminal.svg +4 -0
- data/app/assets/images/parapet/icons/activities/unified-schema-view.svg +4 -0
- data/app/assets/images/parapet/icons/activities/visual-comparison.svg +3 -0
- data/app/assets/images/parapet/icons/activities/visual-summary-bar.svg +5 -0
- data/app/assets/images/parapet/icons/nav/ideas.svg +4 -0
- data/app/assets/images/parapet/icons/nav/languages.svg +8 -0
- data/app/assets/images/parapet/icons/nav/profile.svg +4 -0
- data/app/assets/images/parapet/icons/nav/pulse.svg +7 -0
- data/app/assets/images/parapet/icons/nav/settings.svg +4 -0
- data/app/assets/images/parapet/icons/nav/state.svg +5 -0
- data/app/assets/images/parapet/icons/nav/styles.svg +7 -0
- data/app/assets/images/parapet/icons/nav/tests.svg +4 -0
- data/app/assets/images/parapet/icons/nav/traffic.svg +12 -0
- data/app/assets/javascripts/parapet/activity-card.js +439 -0
- data/app/assets/javascripts/parapet/activity-cards.js +291 -0
- data/app/assets/javascripts/parapet/activity-registry.js +181 -0
- data/app/assets/javascripts/parapet/activity-toggles.js +76 -0
- data/app/assets/javascripts/parapet/application.js +37 -0
- data/app/assets/javascripts/parapet/card-failure-inspector.js +134 -0
- data/app/assets/javascripts/parapet/card-file-editor.js +1722 -0
- data/app/assets/javascripts/parapet/card-progress-area.js +297 -0
- data/app/assets/javascripts/parapet/card-smart-search.js +114 -0
- data/app/assets/javascripts/parapet/card-snippet-library.js +251 -0
- data/app/assets/javascripts/parapet/explorer-resizer.js +92 -0
- data/app/assets/javascripts/parapet/parapet_qss.js +342 -0
- data/app/assets/javascripts/parapet/proficiency-filter.js +53 -0
- data/app/assets/javascripts/parapet/sidebar-resizer.js +197 -0
- data/app/assets/javascripts/parapet/styles-workbench.js +819 -0
- data/app/assets/javascripts/parapet/tree-logic.js +320 -0
- data/app/assets/stylesheets/parapet/_qss_variables.scss +122 -0
- data/app/assets/stylesheets/parapet/parapet_qss.scss +69 -0
- data/app/assets/stylesheets/parapet/qss_application.css +260940 -0
- data/app/assets/stylesheets/parapet/qss_application.css.map +1 -0
- data/app/assets/stylesheets/parapet/qss_application.scss +5 -0
- data/app/assets/stylesheets/parapet/qss_workspace.scss +209 -0
- data/app/assets/stylesheets/parapet/sample.qss +22 -0
- data/app/controllers/parapet/application_controller.rb +54 -0
- data/app/controllers/parapet/base_controller.rb +18 -0
- data/app/controllers/parapet/build_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/css_suite/compiler_controller.rb +95 -0
- data/app/controllers/parapet/css_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/css_suite/preview_controller.rb +46 -0
- data/app/controllers/parapet/dashboard_controller.rb +8 -0
- data/app/controllers/parapet/drafts_controller.rb +30 -0
- data/app/controllers/parapet/explorers_controller.rb +53 -0
- data/app/controllers/parapet/files_controller.rb +208 -0
- data/app/controllers/parapet/footers_controller.rb +24 -0
- data/app/controllers/parapet/intentions_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/landing_controller.rb +14 -0
- data/app/controllers/parapet/monitor_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/pm_suite/publish_controller.rb +16 -0
- data/app/controllers/parapet/polyglot_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/runner_controller.rb +34 -0
- data/app/controllers/parapet/sessions_controller.rb +35 -0
- data/app/controllers/parapet/signal_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/test_suite/dashboard_controller.rb +10 -0
- data/app/controllers/parapet/widgets_controller.rb +33 -0
- data/app/helpers/parapet/application_helper.rb +23 -0
- data/app/helpers/parapet/icon_helper.rb +57 -0
- data/app/jobs/parapet/application_job.rb +4 -0
- data/app/mailers/parapet/application_mailer.rb +6 -0
- data/app/models/parapet/activity.rb +14 -0
- data/app/models/parapet/application_record.rb +5 -0
- data/app/models/parapet/current.rb +6 -0
- data/app/models/parapet/draft_recovery.rb +9 -0
- data/app/models/parapet/internal_record.rb +34 -0
- data/app/models/parapet/run_history.rb +22 -0
- data/app/models/parapet/runtime_setting.rb +8 -0
- data/app/models/parapet/session.rb +7 -0
- data/app/models/parapet/suite_assignment.rb +30 -0
- data/app/models/parapet/user.rb +12 -0
- data/app/services/parapet/activity_phrase_renderer.rb +35 -0
- data/app/services/parapet/activity_writer.rb +26 -0
- data/app/services/parapet/authorization.rb +198 -0
- data/app/services/parapet/consistency_auditor.rb +58 -0
- data/app/services/parapet/directory_scanner.rb +159 -0
- data/app/services/parapet/draft_recovery_store.rb +18 -0
- data/app/services/parapet/explorer_manager.rb +402 -0
- data/app/services/parapet/file_save_service.rb +21 -0
- data/app/services/parapet/git_side_ref_committer.rb +50 -0
- data/app/services/parapet/noun_discovery.rb +26 -0
- data/app/services/parapet/publish_gate.rb +83 -0
- data/app/services/parapet/test_runner.rb +178 -0
- data/app/views/layouts/parapet/application.html.erb +83 -0
- data/app/views/layouts/parapet/landing.html.erb +19 -0
- data/app/views/parapet/activities/_ai_error_explainer.html.erb +33 -0
- data/app/views/parapet/activities/_ai_explainer_widget.html.erb +20 -0
- data/app/views/parapet/activities/_calendar_widget.html.erb +31 -0
- data/app/views/parapet/activities/_code_snippets_widget.html.erb +22 -0
- data/app/views/parapet/activities/_coverage_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_dropdown_test.html.erb +20 -0
- data/app/views/parapet/activities/_explorer_node.html.erb +102 -0
- data/app/views/parapet/activities/_explorer_tree.html.erb +16 -0
- data/app/views/parapet/activities/_file_editor.html.erb +183 -0
- data/app/views/parapet/activities/_file_explorer.html.erb +23 -0
- data/app/views/parapet/activities/_fix_suggestions_widget.html.erb +13 -0
- data/app/views/parapet/activities/_generic.html.erb +6 -0
- data/app/views/parapet/activities/_live_preview.html.erb +13 -0
- data/app/views/parapet/activities/_perspective_filters.html.erb +36 -0
- data/app/views/parapet/activities/_progress_area.html.erb +37 -0
- data/app/views/parapet/activities/_project_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_registry_explorer.html.erb +1 -0
- data/app/views/parapet/activities/_run_history.html.erb +2 -0
- data/app/views/parapet/activities/_smart_search.html.erb +11 -0
- data/app/views/parapet/activities/file_explorer/_perspective_scripts.html.erb +58 -0
- data/app/views/parapet/activities/file_explorer/_perspectives.html.erb +0 -0
- data/app/views/parapet/activities/file_explorer/_viewport_scripts.html.erb +33 -0
- data/app/views/parapet/css_suite/_workbench.html.erb +51 -0
- data/app/views/parapet/dashboards/index.html.erb +113 -0
- data/app/views/parapet/debug_panel.html.erb +79 -0
- data/app/views/parapet/debug_panel_tailwind.html.erb +98 -0
- data/app/views/parapet/erb_patch_test.html.erb +5 -0
- data/app/views/parapet/explorers/show.html.erb +1 -0
- data/app/views/parapet/git_viewer.html.erb +62 -0
- data/app/views/parapet/git_viewer_after.html.erb +14 -0
- data/app/views/parapet/git_viewer_before.html.erb +14 -0
- data/app/views/parapet/git_viewer_governed.html.erb +88 -0
- data/app/views/parapet/git_viewer_governed_after.html.erb +36 -0
- data/app/views/parapet/git_viewer_governed_before.html.erb +36 -0
- data/app/views/parapet/git_viewer_tailwind.html.erb +88 -0
- data/app/views/parapet/git_viewer_thin_wrapper.html.erb +85 -0
- data/app/views/parapet/hierarchical_layout_test.html.erb +85 -0
- data/app/views/parapet/hybrid_stress_test.html.erb +25 -0
- data/app/views/parapet/index.html.erb +35 -0
- data/app/views/parapet/mutation_stress_test.html.erb +82 -0
- data/app/views/parapet/recursive_anchoring_test.html.erb +66 -0
- data/app/views/parapet/sessions/new.html.erb +28 -0
- data/app/views/parapet/shared/_cad_inspector.html.erb +92 -0
- data/app/views/parapet/shared/_discovery_sidebar.html.erb +48 -0
- data/app/views/parapet/shared/_explorer_bar.html.erb +71 -0
- data/app/views/parapet/shared/_footer.html.erb +10 -0
- data/app/views/parapet/shared/_footer_ideas.html.erb +36 -0
- data/app/views/parapet/shared/_footer_languages.html.erb +33 -0
- data/app/views/parapet/shared/_footer_pulse.html.erb +33 -0
- data/app/views/parapet/shared/_footer_state.html.erb +33 -0
- data/app/views/parapet/shared/_footer_styles.html.erb +37 -0
- data/app/views/parapet/shared/_footer_tests.html.erb +39 -0
- data/app/views/parapet/shared/_footer_traffic.html.erb +45 -0
- data/app/views/parapet/shared/_global_nav.html.erb +55 -0
- data/app/views/parapet/shared/_head.html.erb +4 -0
- data/app/views/parapet/shared/_header.html.erb +20 -0
- data/app/views/parapet/shared/_object_inspector.html.erb +76 -0
- data/app/views/parapet/shared/_ribbon_header.html.erb +3 -0
- data/app/views/parapet/shared/_root_selector_modal.html.erb +15 -0
- data/app/views/parapet/shared/_tab_bar.html.erb +62 -0
- data/app/views/parapet/shared/activities_ribbon/_agent_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_analysis_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_architect_group.html.erb +21 -0
- data/app/views/parapet/shared/activities_ribbon/_explorer_group.html.erb +12 -0
- data/app/views/parapet/shared/activities_ribbon/_inspector_group.html.erb +15 -0
- data/app/views/parapet/shared/activities_ribbon/_proficiency_filter.html.erb +24 -0
- data/app/views/parapet/shared/activities_ribbon/_runner_group.html.erb +21 -0
- data/app/views/parapet/shared/activities_ribbon/_spatial_inspector_group.html.erb +18 -0
- data/app/views/parapet/shared/activities_ribbon/_studio_group.html.erb +18 -0
- data/app/views/parapet/shared/activities_ribbon/_stylist_group.html.erb +18 -0
- data/app/views/parapet/shared/explorers/_base.html.erb +107 -0
- data/app/views/parapet/shared/explorers/_css.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_intentions.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_project.html.erb +1 -0
- data/app/views/parapet/shared/explorers/_tests.html.erb +1 -0
- data/app/views/parapet/shared/head/_meta.html.erb +3 -0
- data/app/views/parapet/shared/head/_styles_activities.html.erb +152 -0
- data/app/views/parapet/shared/head/_styles_shared.html.erb +52 -0
- data/app/views/parapet/shared/head/_styles_workspace.html.erb +67 -0
- data/app/views/parapet/shared/ribbon_header/_diagnostic_controls.html.erb +17 -0
- data/app/views/parapet/shared/ribbon_header/_execution_controls.html.erb +28 -0
- data/app/views/parapet/shared/ribbon_header/_focus_search.html.erb +5 -0
- data/app/views/parapet/shared/root_selector/_header.html.erb +10 -0
- data/app/views/parapet/shared/root_selector/_path_input.html.erb +13 -0
- data/app/views/parapet/shared/root_selector/_quick_select.html.erb +20 -0
- data/app/views/parapet/shared/root_selector/_scripts.html.erb +61 -0
- data/app/views/parapet/shared/root_selector/_warning.html.erb +11 -0
- data/app/views/parapet/shared/widgets/_base.html.erb +6 -0
- data/app/views/parapet/shared/widgets/_css.html.erb +79 -0
- data/app/views/parapet/widgets/_explainer.html.erb +17 -0
- data/app/views/parapet/widgets/_hotkeys.html.erb +20 -0
- data/app/views/parapet/widgets/_search.html.erb +68 -0
- data/app/views/parapet/widgets/_spatial_inspector.html.erb +54 -0
- data/app/views/parapet/widgets/_widget-explainer.html.erb +17 -0
- data/app/views/parapet/widgets/_widget-hotkeys.html.erb +20 -0
- data/app/views/parapet/widgets/_widget-search.html.erb +68 -0
- data/app/views/parapet/widgets/_widget-snippets.html.erb +18 -0
- data/app/views/parapet/widgets/_widget-suggestions.html.erb +15 -0
- data/app/views/parapet/widgets/show.html.erb +3 -0
- data/app/views/shared/_footer.html.erb +0 -0
- data/config/parapet/qss_config.yml +117 -0
- data/config/parapet/qss_registry.yml +14 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20260916035300_create_run_histories.rb +16 -0
- data/db/migrate/20261002000000_expand_run_histories_for_activity_tracking.rb +7 -0
- data/db/parapet_migrate/20260929184200_create_parapet_users.rb +12 -0
- data/db/parapet_migrate/20260929184300_create_parapet_sessions.rb +9 -0
- data/db/parapet_migrate/20260930000000_create_parapet_runtime_settings.rb +12 -0
- data/db/parapet_migrate/20260930000100_create_parapet_suite_assignments.rb +14 -0
- data/db/parapet_migrate/20261002000000_create_parapet_activities.rb +16 -0
- data/db/parapet_migrate/20261002000100_create_parapet_draft_recoveries.rb +13 -0
- data/db/parapet_schema.rb +64 -0
- data/db/schema.rb +21 -0
- data/db/schema.released.rb +0 -0
- data/lib/parapet/engine.rb +11 -0
- data/lib/parapet/environment_guard.rb +14 -0
- data/lib/parapet/version.rb +3 -0
- data/lib/parapet-rails.rb +1 -0
- data/lib/parapet.rb +59 -0
- data/lib/tasks/parapet_qss.rake +26 -0
- data/lib/tasks/parapet_tasks.rake +25 -0
- data/lib/tasks/randomize_explorer_health.rb +101 -0
- 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>
|