feat: triage workspace + PAT auth + upgrade infrastructure + seed dataset + OpenAPI hardening#731
feat: triage workspace + PAT auth + upgrade infrastructure + seed dataset + OpenAPI hardening#731aaronlippold wants to merge 536 commits into
Conversation
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 33788689 | Triggered | Generic Password | 4271c98 | spec/requests/api/auth_spec.rb | View secret |
| 33788688 | Triggered | Generic Password | 4271c98 | doc/openapi/paths/api_auth_login.yaml | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
There was a problem hiding this comment.
Pull request overview
This PR modernizes the comment/triage workflow and development seeding by introducing a split-pane triage UI (replacing the modal flow), modular/idempotent seed helpers + dev rake tasks, and a DRY’d comment composer state model (ReplyComposerMixin) with supporting factory/test improvements.
Changes:
- Add split-pane triage experience (queue navigation + rule context panel + extracted triage form) and related API support (
include_rule_content, HTML redirect to triage). - Modularize and validate seed pipeline (SeedHelpers, numbered seed files,
dev:*rake tasks, seed verification specs). - Consolidate composer/triage vocabulary utilities and standardize factories/spec fixtures around new traits.
Reviewed changes
Copilot reviewed 83 out of 83 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/services/import/json_archive_importer_spec.rb | Switch review setup to factories/traits for import lifecycle fixtures. |
| spec/services/import/integration/backup_round_trip_spec.rb | Use factory-created reviews in backup round-trip integration spec. |
| spec/services/export/serializers/backup_serializer_spec.rb | Use factory traits for review lifecycle/duplicate-of serialization coverage. |
| spec/services/export/base_spec.rb | Use factory traits for disposition-related export setup. |
| spec/seeds/seed_pipeline_spec.rb | Add end-to-end seed pipeline spec (tagged, truncation strategy). |
| spec/requests/users_spec.rb | Use review factory traits for “My Comments” request fixtures. |
| spec/requests/rules_spec.rb | Use review factory traits for component JSON sidebar/history tests. |
| spec/requests/reactions_spec.rb | Use factory-created reviews/replies for reaction request specs. |
| spec/requests/rack_attack_spec.rb | Use factory-created review for rack-attack reaction target fixture. |
| spec/requests/projects_import_backup_spec.rb | Use factory-created review for project import backup tests. |
| spec/requests/projects_disposition_matrix_export_spec.rb | Use factory traits for triage-status filtered disposition export tests. |
| spec/requests/projects_create_from_backup_spec.rb | Use factory-created review for include_reviews param coverage. |
| spec/requests/project_comments_aggregate_spec.rb | Use factory traits to build aggregate comment fixtures. |
| spec/requests/components_spec.rb | Add HTML redirect expectation for /components/:id/comments; use review factory. |
| spec/requests/components_disposition_matrix_export_spec.rb | Use review factory traits for component disposition export fixtures. |
| spec/requests/component_reviews_spec.rb | Use :component_comment factory trait for component-scoped review tests. |
| spec/rails_helper.rb | Exclude :seed_pipeline specs by default to avoid parallel DB corruption. |
| spec/models/user_spec.rb | Use review factory traits for imported attribution behavior tests. |
| spec/models/reviews_spec.rb | Standardize review creation on factories/traits across invariants + scopes. |
| spec/models/review_polymorphic_commentable_spec.rb | Use :comment/:component_comment traits for polymorphic coverage. |
| spec/models/review_cross_scope_validations_spec.rb | Use review factory traits for cross-scope validation fixtures. |
| spec/models/reaction_spec.rb | Use review factory traits for reaction invariants and summary tests. |
| spec/models/query_performance_spec.rb | Use factory-created reviews to drive component review query tests. |
| spec/models/project_pending_comment_counts_spec.rb | Use factory traits for pending/total count fixtures. |
| spec/models/paginated_comments_pii_spec.rb | Use review factory traits for paginated comment response/reaction counts. |
| spec/models/components_spec.rb | Add assertions for include_rule_content payload shape + updated_at presence. |
| spec/models/component_pending_comment_counts_spec.rb | Use review factory traits for per-component pending count fixtures. |
| spec/lib/seed_helpers_spec.rb | Add unit specs for SeedHelpers API (seed_xccdf/component/review/reply/status/verify). |
| spec/lib/disposition_matrix_export_spec.rb | Switch to review factory traits for export and CSV defang coverage. |
| spec/javascript/mixins/ReplyComposerMixin.spec.js | Add vitest coverage for unified composer state mixin behaviors. |
| spec/javascript/constants/triageVocabulary.spec.js | Add tests for buildStatusFilterOptions(). |
| spec/javascript/components/triage/TriageQueueNav.spec.js | Add tests for 2D triage queue navigation component. |
| spec/javascript/components/triage/CommentTriageForm.spec.js | Add tests for extracted triage form component validation/events. |
| spec/javascript/components/components/ComponentComments.spec.js | Update ComponentComments tests for split-mode behavior and sorting/filter visibility. |
| spec/javascript/components/components/CommentTriageModal.spec.js | Update modal tests to validate delegation to CommentTriageForm and new doTriage API. |
| spec/factory_specs/stig_deadlock_spec.rb | Add regression spec preventing STIG factory deadlock/import side effects. |
| spec/factory_specs/factory_traits_spec.rb | Add spec verifying new FactoryBot traits across multiple models. |
| spec/factories/stigs.rb | Add :skip_rules trait to disable rule import on STIG factory. |
| spec/factories/stig_rules.rb | Ensure stig_rule factory uses a skip-import STIG by default. |
| spec/factories/rules.rb | Add rule status/locked traits used by tests/seeds. |
| spec/factories/reviews.rb | Add review comment/triage/lifecycle traits + auto-membership wiring. |
| spec/factories/projects.rb | Add project membership traits (:with_admin, :with_members). |
| spec/factories/memberships.rb | Add explicit :viewer trait. |
| spec/factories/components.rb | Add component traits for comment period, PoC, released/locked rules. |
| spec/config/seed_idempotency_spec.rb | Update idempotency checks to match modular seed loader + SeedHelpers. |
| spec/blueprints/rule_blueprint_spec.rb | Use review factory traits for comment summary blueprint tests. |
| spec/blueprints/review_membership_blueprints_spec.rb | Use review factory traits for blueprint output coverage. |
| lib/tasks/dev.rake | Add dev:prime/status/verify/reset seed tasks. |
| lib/seed_helpers.rb | Introduce SeedHelpers module (XCCDF import, component seeding, comment seeding, verify/status). |
| docs/superpowers/plans/2026-05-19-comment-interaction-dry.md | Document DRY plan for composer state/event standardization. |
| docs/development/testing.md | Document available FactoryBot traits and usage patterns. |
| docs/development/seed-system.md | Document seed architecture, commands, and SeedHelpers API. |
| db/seeds/data/00_users.rb | Modular seed: demo admin + role-tier + filler users. |
| db/seeds/data/01_projects.rb | Modular seed: demo projects. |
| db/seeds/data/02_srgs.rb | Modular seed: SRG imports via SeedHelpers. |
| db/seeds/data/03_stigs.rb | Modular seed: STIG imports via SeedHelpers. |
| db/seeds/data/04_components.rb | Modular seed: demo components, overlays, dummy stress components, PoC backfill. |
| db/seeds/data/05_memberships.rb | Modular seed: demo RBAC wiring and counter cache reset. |
| db/seeds/data/06_rule_statuses.rb | Modular seed: vary rule statuses for demo coverage. |
| db/seeds/data/10_comments.rb | Modular seed: demo comment threads + triage states. |
| db/seeds/data/11_cross_project.rb | Modular seed: cross-project comment fixtures + triage examples. |
| app/models/review.rb | Improve sync_commentable_from_rule to backfill rule_id from commentable when needed. |
| app/models/component.rb | Add include_rule_content option to paginated_comments, include updated_at, and serialize rule content. |
| app/controllers/components_controller.rb | Redirect HTML /components/:id/comments to triage; add include_rule_content param handling for JSON. |
| app/javascript/mixins/ReplyComposerMixin.vue | Add unified comment composer state + open/close/post hooks. |
| app/javascript/constants/triageVocabulary.js | Add terminal/single-button status sets + buildStatusFilterOptions(). |
| app/javascript/composables/ruleFieldConfig.js | Add canonical FIELD_LABELS mapping for rule context display. |
| app/javascript/components/users/UserComments.vue | Adopt ReplyComposerMixin; switch truncation to CSS; use shared status options builder. |
| app/javascript/components/shared/CommentThread.vue | Switch date formatting to DateFormatMixin helper and remove ad-hoc formatter. |
| app/javascript/components/rules/RulesCodeEditorView.vue | Adopt ReplyComposerMixin and unify composer open/post handling. |
| app/javascript/components/components/ProjectComponent.vue | Adopt ReplyComposerMixin and unify composer open/post handling. |
| app/javascript/components/components/ComponentTriagePage.vue | Add command bar controls for split-mode context/admin panel; wire split-mode events. |
| app/javascript/components/components/ComponentComments.vue | Add split-mode triage integration; default sort by ID; hide filters in split mode; use status options builder; adopt ReplyComposerMixin. |
| app/javascript/components/components/CommentTriageModal.vue | Embed CommentTriageForm; refactor to doTriage API; use DateFormatMixin. |
| app/javascript/components/components/CommentDedupBanner.vue | Switch date formatting to DateFormatMixin helper and remove ad-hoc formatter. |
| app/javascript/components/triage/TriageQueueNav.vue | Add queue navigation UI (prev/next comment + prev/next rule + jump-to dropdown). |
| app/javascript/components/triage/RuleContextPanel.vue | Add rule context side panel with collapsible sections and related comments list. |
| app/javascript/components/triage/CommentTriageForm.vue | Extract triage form component used by modal + split view. |
| app/javascript/components/triage/TriageSplitView.vue | Add split-pane triage view with optimistic-lock support and admin sidebar actions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| elsif is_stig | ||
| record = Stig.from_mapping(parsed) | ||
| existing = Stig.find_by(stig_id: record.stig_id) | ||
| if existing | ||
| puts " Already exists: #{existing.name} (Stig)" | ||
| return existing | ||
| end | ||
| record.xml = Nokogiri::XML(xml) | ||
| else |
| trait :reply do | ||
| action { 'comment' } | ||
| comment { 'Reply to parent comment' } | ||
| triage_status { nil } | ||
|
|
||
| after(:build) do |review, _evaluator| | ||
| unless review.responding_to_review_id | ||
| parent = create(:review, :comment, user: review.user, rule: review.rule) | ||
| review.responding_to_review_id = parent.id | ||
| review.section = parent.section | ||
| end | ||
| end |
| trait :component_comment do | ||
| action { 'comment' } | ||
| rule { nil } | ||
| section { nil } | ||
| comment { 'Component-level comment' } | ||
|
|
||
| after(:build) do |review| | ||
| unless review.commentable_type == 'Component' | ||
| component = create(:component, :skip_rules) | ||
| review.commentable = component | ||
| review.commentable_type = 'Component' | ||
|
|
||
| if review.user | ||
| project = component.project | ||
| create(:membership, user: review.user, membership: project, role: 'viewer') unless Membership.exists?(user: review.user, membership: project) | ||
| end | ||
| end | ||
| end |
| trait :duplicate do | ||
| triage_status { 'duplicate' } | ||
|
|
||
| after(:build) do |review| | ||
| review.triage_set_by ||= create(:user) | ||
| review.triage_set_at ||= Time.current | ||
| unless review.duplicate_of_review_id | ||
| target = create(:review, :comment, rule: review.rule, user: review.user) | ||
| review.duplicate_of_review_id = target.id | ||
| end | ||
| end |
| comments: { type: Array, required: true }, | ||
| currentId: { type: [Number, String], default: null }, | ||
| }, | ||
| computed: { | ||
| ruleGroups() { | ||
| const groups = []; | ||
| const seen = new Map(); | ||
| for (const c of this.comments) { | ||
| const key = c.rule_id || `component-${c.id}`; | ||
| if (!seen.has(key)) { | ||
| const group = { | ||
| ruleId: key, | ||
| ruleName: c.rule_displayed_name || "(component)", | ||
| comments: [], | ||
| }; | ||
| seen.set(key, group); | ||
| groups.push(group); | ||
| } | ||
| seen.get(key).comments.push(c); | ||
| } | ||
| return groups; | ||
| }, | ||
| currentPosition() { | ||
| for (let gi = 0; gi < this.ruleGroups.length; gi++) { | ||
| const group = this.ruleGroups[gi]; | ||
| for (let ci = 0; ci < group.comments.length; ci++) { | ||
| if (group.comments[ci].id === this.currentId) { | ||
| return { ruleIndex: gi, commentIndex: ci }; | ||
| } | ||
| } | ||
| } | ||
| return { ruleIndex: -1, commentIndex: -1 }; | ||
| }, |
| props: { | ||
| rows: { type: Array, required: true }, | ||
| initialCommentId: { type: [Number, String], required: true }, | ||
| componentId: { type: [Number, String], required: true }, | ||
| effectivePermissions: { type: String, default: null }, | ||
| adminPanelOpen: { type: Boolean, default: false }, | ||
| contextMode: { type: String, default: "commented" }, | ||
| }, | ||
| data() { | ||
| return { | ||
| activeCommentId: this.initialCommentId, | ||
| isDirty: false, | ||
| saving: false, | ||
| conflictAlert: null, | ||
| adminAction: null, | ||
| adminAuditComment: "", | ||
| adminConfirmationId: "", | ||
| adminTargetRuleId: null, | ||
| }; | ||
| }, | ||
| computed: { | ||
| sortedRows() { | ||
| return [...this.rows].sort((a, b) => a.id - b.id); | ||
| }, | ||
| activeComment() { | ||
| return this.sortedRows.find((r) => r.id === this.activeCommentId) || null; | ||
| }, | ||
| canTriage() { |
| it 'creates a review comment using FactoryBot' do | ||
| review = described_class.find_or_seed_review( | ||
| rule: rule, user: user, section: 'check_content', | ||
| comment: 'Test comment for seed helper' | ||
| ) | ||
| expect(review).to be_persisted | ||
| expect(review.action).to eq('comment') | ||
| expect(review.triage_status).to eq('pending') | ||
| end |
| async onTriageSave(decision) { | ||
| await this.doSave(decision, false); | ||
| }, | ||
| async onTriageSaveAndNext(decision) { | ||
| await this.doSave(decision, true); | ||
| }, | ||
| async doSave(decision, advance) { | ||
| if (!this.activeComment) return; | ||
| this.saving = true; | ||
| this.conflictAlert = null; | ||
| try { | ||
| const payload = { | ||
| triage_status: decision.triage_status, | ||
| expected_updated_at: this.activeComment.updated_at, | ||
| }; | ||
| if (decision.response_comment) { | ||
| payload.response_comment = decision.response_comment; | ||
| } | ||
| if (decision.triage_status === "duplicate") { | ||
| payload.duplicate_of_review_id = decision.duplicate_of_review_id; | ||
| } | ||
|
|
||
| const triageRes = await axios.patch(`/reviews/${this.activeComment.id}/triage`, payload); | ||
| this.$emit("triaged", triageRes.data.review); | ||
|
|
||
| if (triageRes.data.response_review) { | ||
| this.$emit("response-posted", { | ||
| parentId: this.activeComment.id, | ||
| responseReview: triageRes.data.response_review, | ||
| }); | ||
| } | ||
|
|
||
| if (!SINGLE_BUTTON_STATUSES.has(decision.triage_status)) { | ||
| const adjRes = await axios.patch(`/reviews/${this.activeComment.id}/adjudicate`, {}); | ||
| this.$emit("adjudicated", adjRes.data.review); | ||
| } | ||
|
|
|
Some notes after review:
Some side notes about locked fields:
|
|
That 3NF doc design is a big enough change to warrant its own PR. Going to cherry pick it and make a new PR off main. |
|
@wdower — heads up, this PR is still in progress. All your review feedback items from the first round are addressed (admin inline, toggle moved, rename, locked comments, staleness badge, etc.) but I still have a few cards to finish:
Will push these in the next session. Don't need a full re-review yet — just wanted you to know the status. — Aaron |
Added/Changed/Fixed sections covering three-column layout, progress bar, DRY color palette, ARIA accessibility, InfoTooltip/InfoNotice adoption, and bug fixes Authored by: Aaron Lippold<lippold@gmail.com>
v2-0re.12 — project/ keeps only AlertMixin (0re.9): - Project.vue: the permissions PROVIDER — derives canAdmin from the roleGteTo util in setup (a provider cannot inject its own provide); six template role_gte_to calls collapsed to canAdmin. DateFormat and Form mixins were dead imports — removed. Prop pass-throughs to CommandBar/Sidepanels/MembersModal deleted (ComponentCard keeps its prop until 0re.13). - ProjectCommandBar/ProjectSidepanels/ProjectMembersModal: effectivePermissions prop -> usePermissions inject (canAdmin/ canEdit); per-component computeds (isAdmin, canEditMetadata, isEditable) replaced by the composable names. - RestoreBackupModal/UpdateMetadataModal: dead FormMixin imports removed (authenticityToken never consumed). - ProjectMembersModal gets its first spec (5 tests, inject tests proven RED first); other specs switched to provide-based mounting with inject requirement tests; obsolete prop-pass assertion rewritten as an attributes()-based provide-contract pin. Live-verified via Playwright on /projects/34 as admin: New Component and visibility toggle render via inject; Members modal fully editable (role dropdowns + Remove); dark+light screenshots reviewed. Authored by: Aaron Lippold<lippold@gmail.com>
v2-y9a — below the navbar expand breakpoint (xl), Bootstrap gives .navbar-nav .dropdown-menu position: static, so opening the user or notification menu inflated the navbar in-flow instead of overlaying the page. BootstrapVue never runs Popper inside navbars, so the boundary=viewport props were no-ops (removed, with their tests rewritten to pin the real contract). Fix scoped to the always-expanded utility nav: re-apply Bootstrap's own expanded-navbar rule (position: absolute) at every width, anchor menus to the nav itself via the documented position-static-parent pattern, cap menu width to the viewport and let item text wrap — this also fixes the bell menu overflowing the left edge at 375px (found during verification). Badge anchor moved to the toggle link. Verified via Playwright at 375/768/992/1440 in both modes: navbar height unchanged when menus open, all menus within viewport. Authored by: Aaron Lippold<lippold@gmail.com>
v2-8lb — the footer action row was a non-wrapping flex container with justify-content-between: at narrow card widths the primary button got squeezed (label split across two lines) while the right-anchored admin group wrapped ragged-left with orphaned buttons. Fix: the toolbar wraps as groups (flex-wrap on the outer row) so the admin buttons drop below the primary as a left-aligned unit; the primary label is text-nowrap; the inline gap style is replaced with scoped gap classes (Bootstrap 4 has no gap utilities — same approach as ProjectCommandBar). Verified via Playwright at 1080 (two-column cards, the reported bug width), 768 and 375 in both modes, on both card variants (released and unreleased action sets). Authored by: Aaron Lippold<lippold@gmail.com>
- ComponentCard + MembershipsTable consume usePermissions via provide/inject - ComponentCard release flow on useConfirmRelease declarative modal - Project.vue effective-permissions pass-throughs removed (provider tree) - DELETED dead components (zero consumers, verified imports+kebab+packs+HAML): ComponentCommandBar.vue, components/MembersModal.vue + their specs - Card: v2-0re.13.1 Authored by: Aaron Lippold<lippold@gmail.com>
- ComponentSettingsPage, CommentComposerModal, AddQuestionsModal, UpdateMetadataModal never consumed authenticityToken - Stale axios-era CSRF comment corrected: ky baseApi sets X-CSRF-Token per request since 447ca1e - Card: v2-0re.13.2 Authored by: Aaron Lippold<lippold@gmail.com>
- Lock/AddComponent/NewComponent modals + NewMembership render hidden CSRF inputs via setup-returned useAuthToken (one source of truth); NewMembership local computed deleted - Add/NewComponentModal also on useDisplayedComponent - FIX: displayed-name comma artifacts (X (Version 2, ) -> X (Version 2)) in composable + mixin lockstep; bug-encoding specs corrected - FIX: duplicate NewProjectAuthenticityToken DOM ids made unique - FIX: dead Pinia HMR block removed from stores/comments.js (esbuild iife import.meta warnings eliminated) - Cards: v2-0re.13.3 Authored by: Aaron Lippold<lippold@gmail.com>
- ComponentComments off DateFormat/Form/RoleComparison/ReplyComposer mixins: usePermissions inject, useDateFormat, useReplyComposer via composerBridge (late-bound onOpen/afterPosted — Vue 2.7 setup cannot reach the instance without getCurrentInstance) - Parent pass-throughs removed (both triage pages already provide) - DELETED CommentTriageModal.vue (578 lines): retired by e640b15 when admin actions moved into TriageSplitView; zero consumers since - Card: v2-0re.13.4 Authored by: Aaron Lippold<lippold@gmail.com>
- 2 dead imports removed (DateFormat, RoleComparison — component is the effectivePermissions PROVIDER); 3 real migrations: useSortRules, useConfirmRelease (declarative modal — msgBoxConfirm pattern fully retired), useReplyComposer via composerBridge - Closes the v2-0re.13 epic: components/ + memberships/ mixin-free except AlertMixin (0re.9) - Card: v2-0re.13.5 Authored by: Aaron Lippold<lippold@gmail.com>
- Resources > DISA Process Guide now links directly to /disa-guide/vendor-stig-process-guide (was the bare /disa-guide) - New ApplicationHelper spec covers base_navigation links Authored by: Aaron Lippold<lippold@gmail.com>
- applier_spec audit describes opt in via include_context 'with auditing' (auditing is globally disabled in tests since 2439dd4; these specs predate that and were rebased onto it without a re-run) - No plumbing bug: both merge audit paths already share sync_id; the mismatched uuids came from factory initial-rule audits that bypass the disabled flag - benchmark fixture locked_fields [] -> {} (JSONB hash is the contract; array shape was masked until 15461bd correctly removed the tolerant cast that hid a silent lock bypass) - Full suite: 3688 examples, 0 failures - Card: v2-480.44 Authored by: Aaron Lippold<lippold@gmail.com>
- Mirrors 20260526120000 (component_metadata) exactly; reversible - All consumers are key-based access; no json-text semantics anywhere - Card: v2-qkv Authored by: Aaron Lippold<lippold@gmail.com>
- handleSessionExpired (exported, testable): 401 -> location.reload(); the navigational request lets Devise FailureApp set the cause-specific flash (timeout / signed-in-elsewhere / unauthenticated) and store user_return_to — replacing the hardcoded sign-in jump that lost both - 3 vitest cases; live-proven: dead session -> ajax -> sign-in redirect -> re-login returned to the interrupted page - Card: v2-7r3 Authored by: Aaron Lippold<lippold@gmail.com>
- Aaron-approved design (D1-D6): wire the vulcan three-tier system into Bootstrap's own variable layer; root cause = stock Bootstrap compiled with zero pre-import customization, design system bolted on as ~70 selector overrides - Appendix: full BS4 variable inventory, var() injection safety map, override census, token gap/drift tables (3-agent review evidence) - Implementation chain: v2-g4n.6-.9 - Card: v2-g4n.4 (closed) Authored by: Aaron Lippold<lippold@gmail.com>
- 14 components migrated TDD-first (v2-0re.14): DateFormat, SortRules, HistoryGrouping, HumanizedTypes, FindAndReplace, FormFeedback, and ReplyComposer mixins replaced by their composables - RulesCodeEditorView: usePermissions inject replaces the effectivePermissions prop; Rules.vue pass-through removed; comment composer wired via the late-bound composerBridge in created() - Dead imports removed via per-file member verification: FormMixin x4 (incl. NewRuleModalForm — authenticityToken never referenced), DateFormat x2, RoleComparison x1, AlertMixin x2 (unused members) - RuleRevertModal: unlisted HumanizedTypesMixIn found and migrated - RuleEditorHeader: strict equality for admin/reviewer role checks - FormFeedback forms declare validFeedback/invalidFeedback props explicitly (prop API parity with the removed mixin) - New specs: RuleHistories, CheckForm, RuleDescriptionForm, NewRuleModalForm; composable contract tests added across rules/ specs - Live tested via Playwright: editor both modes, section/reply composer, reviews dates, find-and-replace grouping (432 results in 74 controls) Authored by: Aaron Lippold<lippold@gmail.com>
- Describe blocks named by content, comments explain why in plain terms; card-to-code mapping lives in git history and the board Authored by: Aaron Lippold<lippold@gmail.com>
- lint + lint:ci scripts and the lefthook pre-commit glob now cover spec/javascript alongside app/javascript; CI inherits via lint:ci - prettier autofix across the spec suite (previously unlinted) - RuleDetails spec: console.error capture via vi.spyOn instead of manual reassignment (no-console clean, matches suite convention) - usePermissions spec: single shared test harness component (vue/one-component-per-file clean) Authored by: Aaron Lippold<lippold@gmail.com>
- Models, jobs, merge-engine services, migrations, and specs: comments and describe/it titles named by content; card-to-code mapping lives in git history and the board - Comment-only changes — verified with rubocop (34 files clean) and the touched specs (271 examples, 0 failures) Authored by: Aaron Lippold<lippold@gmail.com>
- Vulcan/CommentTracker (RuboCop) and comment-tracker (ESLint) only matched the legacy long-form prefixes (vulcan-clean-, vulcan-v2.x-, vulcan-v3.x-); the board moved to a short prefix and both rules silently matched nothing — the root cause of tracker IDs accumulating in code and tests - Both patterns now also match the short-form board prefix; ESLint side derives its matcher and all three strip patterns from ONE source string so they cannot drift again - Spec fixtures use synthetic card ids; verified clean repo-wide (rubocop --only Vulcan/CommentTracker: 649 files, 0 offenses; lint:ci zero warnings) Authored by: Aaron Lippold<lippold@gmail.com>
- Sweep of card ids written WITHOUT the board prefix (plain epic.child fragments) that the prefix-based sweep and lint rules cannot see — comments now name the work by content - Comment-only changes; verified with the touched specs (179 examples, 0 failures), rubocop, and lint:ci Authored by: Aaron Lippold<lippold@gmail.com>
- Final consumer-migration group before the toast architecture: UserComments (useDateFormat + useReplyComposer via the late-bound composerBridge), ProjectsTable (useDateFormat), TriageSplitView (usePermissions inject - canEdit gates triage, canAdmin gates the inline admin actions; effectivePermissions prop deleted and the ComponentComments pass-through removed) - FormMixin was verified dead in ALL 11 listed consumers (authenticityToken never referenced) and removed; ProjectsTable's stale axios-era CSRF justification corrected - baseApi hooks own CSRF since the ky migration; navbar App now carries no mixins - TriageSplitView spec converted to provide-based mountView helper (51 mounts); mixin-contract tests added across the group; four new spec files: CreateTokenModal, UserPasswordPage, NewProjectModal, UpdateProjectDetailsModal - Live tested via Playwright: triage split view (admin actions render via inject), My Comments, project list dates, users table, navbar - both modes, zero console errors Authored by: Aaron Lippold<lippold@gmail.com>
ky v2 parses the error body into error.data and consumes the Response doing it, so re-parsing error.response always threw and the catch nulled the payload — every 4xx/5xx toast showed the generic fallback instead of the server's canonical message. Use error.data and add normalization tests that model the consumed-body reality. Authored by: Aaron Lippold<lippold@gmail.com>
esbuild builds each pack as an isolated iife, so module state never crosses packs — producers dispatch vulcan:toast CustomEvents with plain data and Toaster.vue (toaster pack, on every page) is the single bvToast renderer, building VNode bodies (paragraph arrays, permission denied admin lists) where a render context exists. Replaces AlertMixin behavior 1:1: alertOrNotifyResponse, arrayToMessage, permissionDeniedBody, plus showToast/showSuccess/showError/showWarning. Authored by: Aaron Lippold<lippold@gmail.com>
40 components across components/, project/, projects/, memberships/, rules/, shared/, triage/, and users/ now destructure alertOrNotifyResponse from useToast() in setup(); call sites unchanged. Two dead imports removed (UpdateFromSpreadsheetModal, BenchmarkViewer — imported the mixin, never used a member). AlertMixin.vue deleted; its behavior contract lives on in useToast.spec and Toaster.spec. Spec mixin-contract tests updated to the new requirement: no mixins, toast handler provided by the composable. Authored by: Aaron Lippold<lippold@gmail.com>
Projects.vue called .catch(this.alertOrNotifyResponse) without ever declaring AlertMixin — the handler was undefined, so a failed project refresh rethrew silently with no toast. Wired via useToast like every other consumer. Authored by: Aaron Lippold<lippold@gmail.com>
All 13 mixins are converted to composables or verified dead; the 10 remaining files had zero consumers. Their 6 spec files tested dead code whose behavior contracts live in the composable specs (useConfirmRelease, useDisplayedComponent, useFindAndReplace, useHistoryGrouping, useHumanizedTypes, useReplyComposer). Verified: grep 'mixins:' app/javascript/components/ and grep 'from.*mixins/' app/javascript/ both return zero. Authored by: Aaron Lippold<lippold@gmail.com>
The settings page help bullets were hardcoded prose duplicating the phase/reason vocabulary, so the radio options and the explanatory card could drift apart. COMMENT_PHASE_HELP, CLOSED_REASON_HELP, and commentPhaseHelpItems() now live beside the labels in triageVocabulary; the card renders a v-for over them, headings composed via commentPhaseStatusText. Characterization tests pin the exact rendered copy and a drift-guard test asserts the DOM bullets match the vocabulary 1:1. This file also carries the page's AlertMixin-to-useToast wiring (same-file change from the consumer migration). Authored by: Aaron Lippold<lippold@gmail.com>
Devise's default after-sign-out path (root) triggered a second auth redirect that consumed the 'Signed out successfully.' flash before the sign-in page rendered. Added the Devise-documented after_sign_out_path_for override returning new_user_session_path. Also strengthened the AC-12(02)/V-222392 compliance spec, which followed both redirects and only asserted the Toaster prop NAME existed — it passed while the logoff message was lost; it now pins the redirect target, the flash value, and the message in the rendered body. Authored by: Aaron Lippold<lippold@gmail.com>
The navbar signed out via an ajax JSON DELETE — Devise returns a flashless 204 for non-navigational formats — then jumped to root client-side, so the AC-12(02) logoff message could never appear no matter what the server did. Sign Out is now a standard rails-ujs data-method=delete link (ujs is started globally in the application pack), running Devise's full HTML flash+redirect flow. The dead ajax handler and authApi.signOut are removed with their tests. Authored by: Aaron Lippold<lippold@gmail.com>
Regression guard for the silent-failure bug fixed in b40f754: the page referenced this.alertOrNotifyResponse without any provider, so the .catch handler was undefined. Asserts the handler is a function (fails against the pre-fix code) and that a failed getProjects routes through it. Authored by: Aaron Lippold<lippold@gmail.com>
|




Summary
Major feature branch: 247 commits, 668 files changed, 53K insertions. Builds on the triage split-pane workspace (PR #717 follow-up) and adds infrastructure standardization, API authentication, upgrade system, seed data, and documentation.
Core Features (earlier commits on this branch)
New in this push
{title, message: Array, variant}across all mutation endpointsvulcan_development/test/productionnaming, test DB name hardcoded to prevent collision, port registry (5435)drop_invalid_reviewsnow deletes children before parents (RESTRICT safety)Bug fixes
Test plan
bin/parallel_rspec spec/— full backend suiteyarn test:unit— Vue component testsyarn lint:ci— 0 warningsbundle exec rubocop— 0 offensesyarn openapi:bundle && yarn openapi:lint— 0 errorsbundle exec rspec spec/contracts/— 107 contract testsyarn docs:build— VitePress builds cleanbundle exec brakeman— 0 warningsAuthored by: Aaron Lippoldlippold@gmail.com