Implements norman's approved Direction A (docs/plans/phase-8-detail-ux.md): a
projax item is a page you READ, with editing as a deliberate mode.
- READ MODE (default): content_md renders as MARKDOWN via goldmark (pure-Go,
no cgo; SAFE-by-default — raw HTML omitted, no separate sanitizer needed;
GFM for tables/strikethrough/autolinks). A compact chip header (status /
management / tags / pinned / archived) + a single Edit toggle. Kills D1 (no
read mode) and D7 (title/status rendered twice).
- EDIT MODE (?edit=1): the identity+content form (progressive-enhancement
baseline — full-page toggle, no JS). Save → POST → back to read; Cancel
returns to read. Rare settings (Public listing / Timeline behaviour) stay
nested <details> disclosures inside the form (B's accordion discipline).
- WORK CARDS: Tasks (the Phase 7c unified list, dropped in UNCHANGED), Issues,
Documents render as always-visible cards below the description, in BOTH modes
(they were always HTMX-independent of the form). One visible card-title each;
the in-partial h2 stays visually-hidden (Slice 0); #…-section ids + outerHTML
swap targets preserved.
- Retired under A (Q6): the top-level proj-section collapse model + its
localStorage persistence script + 'reset section state' link + the
aux-divider/'Related' framing. Kept the secondary sub-disclosures (done
tasks / closed issues) inside the partials.
- CSS: new .detail (760px reading column), .detail-card, .card-title,
.markdown-body, .btn/.edit-toggle — all within the existing design tokens,
no foreign system. Phone lands on description→tasks (settings behind Edit).
Tests: TestRenderMarkdown{,Empty,Safe,GFM}; rewrote the detail tests for the
read/edit split (TestDetailReadModeRendersMarkdown, TestDetailEditFieldsRender
InOrder, TestDetailWorkCardsAfterForm, TestDetailReadModeIsNotCollapsibles,
TestDetailEditModeKeepsSettingsDisclosures, updated TestDetailNoDoubleHeader +
public-listing). Only pre-existing failures remain (TestParityListAll,
TestProjectFilter*/TestTimeline* — route-drift on 6436b52).
75 lines
2.7 KiB
Go
75 lines
2.7 KiB
Go
package web_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestDetailNoDoubleHeader is the Phase 8 Slice 0 guard for m's literal
|
|
// complaint: each aux section showed TWO headers. Under Direction A the work
|
|
// sections are always-visible cards whose <h2 class="card-title"> is the single
|
|
// VISIBLE header; the partial's own <h2> is visually-hidden (it survives only
|
|
// as the a11y label for the standalone HTMX swap fragment). Documents always
|
|
// renders, so we assert on it.
|
|
func TestDetailNoDoubleHeader(t *testing.T) {
|
|
srv, pool := mustServer(t)
|
|
defer pool.Close()
|
|
h := srv.Routes()
|
|
_, body := get(t, h, "/i/dev")
|
|
// The visible card header.
|
|
if !strings.Contains(body, `class="card-title"`) {
|
|
t.Errorf("expected a visible card-title header on the read view")
|
|
}
|
|
// The in-partial h2 must be hidden (a11y label for the swap fragment).
|
|
if !strings.Contains(body, `<h2 class="visually-hidden">Documents</h2>`) {
|
|
t.Errorf("expected the Documents partial h2 to be visually-hidden")
|
|
}
|
|
// A bare, visible <h2>Documents</h2> would be the double-header regression.
|
|
if strings.Contains(body, `<h2>Documents</h2>`) {
|
|
t.Errorf("found a visible <h2>Documents</h2> — the double header has returned")
|
|
}
|
|
}
|
|
|
|
// TestDetailReadModeIsNotCollapsibles proves Direction A retired the top-level
|
|
// <details> collapse model + its localStorage persistence script: the read
|
|
// view renders work cards, not proj-section accordions, and the per-section
|
|
// toggle script is gone.
|
|
func TestDetailReadModeIsNotCollapsibles(t *testing.T) {
|
|
srv, pool := mustServer(t)
|
|
defer pool.Close()
|
|
h := srv.Routes()
|
|
code, body := get(t, h, "/i/dev")
|
|
if code != 200 {
|
|
t.Fatalf("GET /i/dev → %d", code)
|
|
}
|
|
if !strings.Contains(body, `id="documents-card"`) {
|
|
t.Errorf("read view missing the Documents work card")
|
|
}
|
|
// The retired collapse-persistence script must be gone from the read view.
|
|
if strings.Contains(body, `projax.section.`) {
|
|
t.Errorf("the per-section localStorage collapse script should be retired under Direction A")
|
|
}
|
|
if strings.Contains(body, `proj-section-reset`) {
|
|
t.Errorf("the 'reset section state' link should be retired under Direction A")
|
|
}
|
|
}
|
|
|
|
// TestDetailEditModeKeepsSettingsDisclosures proves the rare settings
|
|
// (public-listing / timeline-behaviour) remain nested <details> disclosures
|
|
// INSIDE the edit form (B's accordion discipline within edit mode).
|
|
func TestDetailEditModeKeepsSettingsDisclosures(t *testing.T) {
|
|
srv, pool := mustServer(t)
|
|
defer pool.Close()
|
|
h := srv.Routes()
|
|
_, body := get(t, h, "/i/dev?edit=1")
|
|
for _, want := range []string{
|
|
`data-section="public"`,
|
|
`data-section="timeline-behaviour"`,
|
|
`class="proj-section-summary"`,
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("edit form missing settings disclosure %q", want)
|
|
}
|
|
}
|
|
}
|