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,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, '"')}">
|
|
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
|
+
})();
|