diff --git a/internal/web/handlers_test.go b/internal/web/handlers_test.go index 1c656cb..9d60956 100644 --- a/internal/web/handlers_test.go +++ b/internal/web/handlers_test.go @@ -234,6 +234,9 @@ func TestDetailRendersHighlightsAndTakeaways(t *testing.T) { require.Contains(t, html, "takeaway one") require.Contains(t, html, `id="action-buttons"`, "action button group present") require.Contains(t, html, "Watched") + require.Contains(t, html, `class="segmented"`, "watched/skipped render as a segmented control (UX review C5)") + require.Less(t, strings.Index(html, "Skipped"), strings.Index(html, "Saved"), + "Saved sits after the watched/skipped segment") require.Contains(t, html, "← Summaries", "back link to the list (UX review C3)") // The detail page leads with the attention-saving payload (UX review A8): diff --git a/internal/web/view.go b/internal/web/view.go index f435ba8..5125e3b 100644 --- a/internal/web/view.go +++ b/internal/web/view.go @@ -684,8 +684,13 @@ a.btn, a.btn:visited { color: var(--accent-fg); } .detail ul { margin: 0; padding-left: 1.2rem; line-height: 1.6; } .detail li { margin-bottom: var(--s1); } -/* action toggles */ -.actions { display: flex; gap: var(--s2); margin: var(--s4) 0; flex-wrap: wrap; } +/* action toggles — watched|skipped form one segmented control (they are mutually + exclusive), "saved" sits apart as an independent toggle */ +.actions { display: flex; gap: var(--s3); margin: var(--s4) 0; flex-wrap: wrap; align-items: center; } +.segmented { display: inline-flex; } +.segmented .action { border-radius: 0; border-right-width: 0; } +.segmented .action:first-child { border-top-left-radius: var(--radius); border-bottom-left-radius: var(--radius); } +.segmented .action:last-child { border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius); border-right-width: 1px; } .actions .action { font: inherit; padding: .4rem .9rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--card); color: var(--fg); cursor: pointer; transition: border-color .15s, background .15s; } .actions .action:hover { border-color: var(--accent); } .actions .action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } diff --git a/internal/web/views.templ b/internal/web/views.templ index 9ab4991..ed34a72 100644 --- a/internal/web/views.templ +++ b/internal/web/views.templ @@ -530,20 +530,31 @@ templ ActionButtons(videoID string, active map[string]bool) { hx-target="#action-buttons" hx-swap="outerHTML" > - for _, v := range actionVerbs { - - } + // watched ↔ skipped are mutually exclusive (the store clears one when the + // other is set), so they read as a single segmented choice. "saved" is an + // independent toggle and sits apart (UX review C5). + + @actionButton("watched", active["watched"]) + @actionButton("skipped", active["skipped"]) + + @actionButton("saved", active["saved"]) } + +// actionButton is one toggle button in the action group: a submit carrying its +// verb, marked active (accent fill + ✓ prefix + aria-pressed) when currently set. +templ actionButton(verb string, isActive bool) { + +} diff --git a/internal/web/views_templ.go b/internal/web/views_templ.go index 7775c59..9dca6bb 100644 --- a/internal/web/views_templ.go +++ b/internal/web/views_templ.go @@ -1862,86 +1862,127 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, v := range actionVerbs { - var templ_7745c5c3_Var81 = []any{"action", templ.KV("active", active[v])} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var81...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var87 string + templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(verb)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 557, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }