gabriel / musehub public
feat BREAKING proposals task/proposal-list-routes #1 / 1
AI Agent gabriel · 128 days ago · May 8, 2026 · Diff

feat(proposals): Phase 3 UI route extensions and fragment templates

Extend proposal_list_page with full ProposalListFilters params and enrich_proposal_list_batch so every rendered row carries the complete ProposalListEntry payload (risk bands, domain dots, approval status, etc.). Heat and readiness data fetched in parallel via asyncio.gather.

Add three new routes to ui_proposals.py: GET /{owner}/{repo}/proposals/rows — HTMX bare fragment; non-HTMX → 302 GET /{owner}/{repo}/proposals/{id}/summary — single-row expansion panel; 404 on miss GET /{owner}/{repo}/proposals/heat — domain heat bar fragment for #domain-heat swap

Add two new Jinja2 fragment templates: fragments/proposal_row_detail.html — risk breakdown, approval status, touched symbols fragments/domain_heat.html — per-domain count + avg-risk bars

Update test_musehub_ui_proposal_ssr.py: replace stale merge-button test (removed in MSign read-only cleanup) with CLI-hint assertion.

Add 29 Tier-3 (E2E) + Tier-7 (security) tests: list page renders, HTMX fragment swap, row summary 200/404, heat fragment, input validation (state/ sort/author_type/limit/assigned_reviewer pattern), 401 on private repos.

sha256:296ce5184347e66a0d897dc3cb7656845c6288907936f83c150b19357e6e7174 sha
+53 ~4 −2 symbols
sha256:4a4d60bd78dac7a2a5c5f8a28abcba337782491bcdda7425fc40f2cf87e1c802 snapshot
+53
symbols added
~4
symbols modified
−2
symbols removed
0
dead code introduced
Semantic Changes 59 symbols
~ tests/test_proposal_list_phase3.py .py 47 symbols added
+ TestE2EDomainHeatFragment class class TestE2EDomainHeatFragment L226–252
+ test_heat_fragment_no_html_shell method async_method test_heat_fragment_no_html_shell L238–244
+ test_heat_fragment_returns_200 method async_method test_heat_fragment_returns_200 L230–235
+ test_heat_fragment_state_param_accepted method async_method test_heat_fragment_state_param_accepted L247–252
+ TestE2EProposalListPage class class TestE2EProposalListPage L63–120
+ test_list_page_contains_proposal_rows_container method async_method test_list_page_contains_proposal_rows_container L75–82
+ test_list_page_domain_heat_section_present method async_method test_list_page_domain_heat_section_present L112–120
+ test_list_page_returns_200 method async_method test_list_page_returns_200 L67–72
+ test_list_page_risk_band_filter_accepted method async_method test_list_page_risk_band_filter_accepted L104–109
+ test_list_page_risk_desc_sort_accepted method async_method test_list_page_risk_desc_sort_accepted L96–101
+ test_list_page_state_merged_shows_merged_proposals method async_method test_list_page_state_merged_shows_merged_proposals L85–93
+ TestE2EProposalRowSummary class class TestE2EProposalRowSummary L185–219
+ test_summary_does_not_contain_full_html_shell method async_method test_summary_does_not_contain_full_html_shell L210–219
+ test_summary_returns_200_for_existing_proposal method async_method test_summary_returns_200_for_existing_proposal L189–197
+ test_summary_returns_404_for_unknown_proposal method async_method test_summary_returns_404_for_unknown_proposal L200–207
+ TestE2EProposalRowsFragment class class TestE2EProposalRowsFragment L127–178
+ test_fragment_accepts_filter_params method async_method test_fragment_accepts_filter_params L157–165
+ test_fragment_proposal_title_in_response method async_method test_fragment_proposal_title_in_response L168–178
+ test_htmx_request_returns_fragment method async_method test_htmx_request_returns_fragment L131–142
+ test_non_htmx_request_redirects method async_method test_non_htmx_request_redirects L145–154
+ TestSecurityApiEndpointValidation class class TestSecurityApiEndpointValidation L355–380
+ test_api_invalid_sort_rejected method async_method test_api_invalid_sort_rejected L359–364
+ test_api_invalid_state_rejected method async_method test_api_invalid_state_rejected L375–380
+ test_api_limit_above_max_rejected method async_method test_api_limit_above_max_rejected L367–372
+ TestSecurityListPageValidation class class TestSecurityListPageValidation L259–321
+ test_assigned_reviewer_too_long_rejected method async_method test_assigned_reviewer_too_long_rejected L313–321
+ test_assigned_reviewer_with_spaces_rejected method async_method test_assigned_reviewer_with_spaces_rejected L303–310
+ test_invalid_author_type_rejected method async_method test_invalid_author_type_rejected L279–284
+ test_invalid_sort_rejected method async_method test_invalid_sort_rejected L271–276
+ test_invalid_state_rejected method async_method test_invalid_state_rejected L263–268
+ test_limit_above_max_rejected method async_method test_limit_above_max_rejected L287–292
+ test_limit_zero_rejected method async_method test_limit_zero_rejected L295–300
+ TestSecurityPrivateRepo class class TestSecurityPrivateRepo L324–352
+ test_private_repo_heat_endpoint_returns_401 method async_method test_private_repo_heat_endpoint_returns_401 L339–344
+ test_private_repo_proposals_returns_401_not_404 method async_method test_private_repo_proposals_returns_401_not_404 L328–336
+ test_private_repo_readiness_endpoint_returns_401 method async_method test_private_repo_readiness_endpoint_returns_401 L347–352
+ _make_repo function async_function _make_repo L33–56
+ _now function function _now L29–30
+ AsyncClient import import AsyncClient L18–18
+ AsyncSession import import AsyncSession L19–19
+ annotations import import annotations L12–12
+ create_proposal import import create_proposal L22–22
+ datetime import import datetime L15–15
+ dbm import import dbm L21–21
+ pytest import import pytest L17–17
+ timezone import import timezone L15–15
+ uuid import import uuid L14–14
~ musehub/api/routes/musehub/ui_proposals.py .py 5 symbols added, 4 symbols modified
+ _build_list_ctx function async_function _build_list_ctx L41–128
+ ProposalListFilters import import ProposalListFilters L30–30
+ proposal_domain_heat_fragment function async_function proposal_domain_heat_fragment L269–296
+ proposal_row_summary function async_function proposal_row_summary L222–262
+ proposal_rows_fragment function async_function proposal_rows_fragment L181–215
~ tests/test_musehub_ui_proposal_ssr.py .py 1 symbol added, 2 symbols removed
test_proposal_detail_merge_button_disabled_when_not_mergeable function async_function test_proposal_detail_merge_button_disabled_when_not_mergeable L203–214
test_proposal_detail_merge_button_has_hx_post function async_function test_proposal_detail_merge_button_has_hx_post L188–200
+ test_proposal_detail_shows_cli_hint function async_function test_proposal_detail_shows_cli_hint L187–199
← Older Oldest on task/proposal-list-routes
All commits
Newer → Latest on task/proposal-list-routes

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:296ce5184347e66a0d897dc3cb7656845c6288907936f83c150b19357e6e7174 --body "your comment"