Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Reusable server-rendered UI components live in `internal/server/templates/compon
- `breadcrumb`: subtle entity hierarchy navigation backed by `uiBreadcrumbData`. Project pages show `Projects / Project name / Current view`; issue pages append the current issue key to the project, with a parent issue key between them for sub-issues. The issue Context manager links the issue key and appends `Context` as the current item.
- `tab-bar`: single-line sibling-view navigation with optional Lucide icons, backed by `uiTabBarData`. Set an item's `MobileOverflow` flag when its owning page provides an equivalent constrained-screen overflow-menu link; those tabs return at `lg`.
- `sidebar-favorites`: shell sidebar favorite-project shortcuts backed by `uiSidebarFavoritesData`. Keep it directly below the `Projects` nav item with a subtle divider from standard navigation, and refresh it with OOB HTMX swaps when favorite state changes.
- `legal-links`: compact links to the canonical Terms, Privacy, and Security pages. Reuse it on signed-out pages and in the authenticated shell so the published documents remain consistently reachable.
- `legal-links`: compact links to the canonical Terms, Privacy, and Security pages. Reuse it on signed-out pages and in the authenticated Settings page so the published documents remain consistently reachable.
- `issue-list-controls`: collapsible shared status, priority, tag, assignee, sort, and direction controls for issue list views. Closed by default; summary shows active filter count plus current sort/direction. Sort uses dropdown options including due date; direction uses Asc/Desc dropdown options with arrow icons. Expects `uiIssueControlsData`; omit tag fields for cross-project lists and omit assignee fields for current-user scoped lists.

## Controls
Expand Down
2 changes: 0 additions & 2 deletions frontend/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ html[data-mobile-sidebar-open] [data-mobile-sidebar] { visibility: visible; tran
#sidebar-toggle:checked ~ .app-shell [data-sidebar-action] { justify-content: center; gap: 0; }
html[data-sidebar-collapsed] .app-shell [data-sidebar-favorite],
#sidebar-toggle:checked ~ .app-shell [data-sidebar-favorite] { justify-content: center; gap: 0; padding-left: 0.5rem; padding-right: 0.5rem; }
html[data-sidebar-collapsed] .app-shell [data-sidebar-legal],
#sidebar-toggle:checked ~ .app-shell [data-sidebar-legal] { display: none; }
html[data-sidebar-collapsed] .app-shell [data-member-summary],
#sidebar-toggle:checked ~ .app-shell [data-member-summary] { justify-content: center; gap: 0; padding-left: 0.25rem; padding-right: 0.25rem; }
html[data-sidebar-collapsed] .app-shell [data-member-menu],
Expand Down
2 changes: 1 addition & 1 deletion internal/server/preview_terms_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestPublicLegalPagesAndLinks(t *testing.T) {
}{
{path: "/login"},
{path: "/signup"},
{path: "/projects", token: e.authToken},
{path: "/settings", token: e.authToken},
} {
body := e.uiGet(t, tc.path, tc.token)
for _, href := range []string{`href="/terms"`, `href="/privacy"`, `href="/security"`} {
Expand Down
2 changes: 1 addition & 1 deletion internal/server/static/app.css

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions internal/server/templates/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ <h3 id="passkey-password-title" class="text-base font-semibold text-slate-950 da
</div>
{{end}}
</section>

<section aria-labelledby="settings-legal-title" class="rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900 lg:col-span-2">
<div class="flex flex-wrap items-center justify-between gap-4">
<div>
<h2 id="settings-legal-title" class="text-sm font-semibold uppercase text-slate-500 dark:text-slate-400">Legal</h2>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">Review the terms, privacy notice, and security policy for trackslash.</p>
</div>
{{template "legal-links"}}
</div>
</section>
</section>
</section>
{{end}}
4 changes: 0 additions & 4 deletions internal/server/templates/shell_sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@
</nav>
{{end}}

<div data-sidebar-legal class="border-t border-slate-200 px-3 py-3 dark:border-slate-800">
{{template "legal-links"}}
</div>

{{if not .Anonymous}}
<details data-close-on-outside class="relative border-t border-slate-200 p-2 dark:border-slate-800">
<summary data-member-summary aria-label="Account menu" class="flex cursor-pointer list-none items-center gap-3 rounded-lg px-2 py-2 hover:bg-slate-100 dark:hover:bg-slate-800">
Expand Down
19 changes: 18 additions & 1 deletion internal/server/ui_account_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,28 @@ func TestUISettingsPageUpdatesProfileAndPassword(t *testing.T) {
}

body := e.uiGet(t, "/settings", token.RawToken)
for _, want := range []string{"Settings", "Display name", "Email", "Password login", "On", "Current password", "New password", "Passkeys", "Saved passkeys", "Add a passkey", "Passkey label", "Enter current password", "Required before changing passkeys.", "Continue", "Add passkey", "No passkeys added."} {
for _, want := range []string{"Settings", "Display name", "Email", "Password login", "On", "Current password", "New password", "Passkeys", "Saved passkeys", "Add a passkey", "Passkey label", "Enter current password", "Required before changing passkeys.", "Continue", "Add passkey", "No passkeys added.", "Legal", "Review the terms, privacy notice, and security policy for trackslash.", `aria-label="Legal"`, `href="/terms"`, `href="/privacy"`, `href="/security"`} {
if !strings.Contains(body, want) {
t.Fatalf("settings body missing %q: %s", want, body)
}
}
if got := strings.Count(body, `aria-label="Legal"`); got != 1 {
t.Fatalf("settings legal navigation count = %d, want 1: %s", got, body)
}
sidebarStart := strings.Index(body, `<aside id="app-sidebar"`)
if sidebarStart < 0 {
t.Fatalf("settings body missing sidebar: %s", body)
}
sidebarEnd := strings.Index(body[sidebarStart:], `</aside>`)
if sidebarEnd < 0 {
t.Fatalf("settings body has unterminated sidebar: %s", body)
}
sidebar := body[sidebarStart : sidebarStart+sidebarEnd]
for _, notWant := range []string{`aria-label="Legal"`, `href="/terms"`, `href="/privacy"`, `href="/security"`} {
if strings.Contains(sidebar, notWant) {
t.Fatalf("settings sidebar still contains legal link %q: %s", notWant, sidebar)
}
}
if strings.Contains(body, "Disable password login") || strings.Contains(body, "Enable password login") {
t.Fatalf("settings body shows password login toggle without passkey: %s", body)
}
Expand Down
14 changes: 7 additions & 7 deletions internal/server/ui_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,6 @@ func TestUIShellRendersResponsiveAccessibleSidebar(t *testing.T) {
`data-member-summary`,
`data-member-label`,
`data-member-menu`,
`data-sidebar-legal`,
`html[data-sidebar-collapsed] .app-shell [data-sidebar-legal]`,
`#sidebar-toggle:checked ~ .app-shell [data-sidebar-legal] { display: none; }`,
`>@demo<`,
`data-sidebar-link data-sidebar-view="projects"`,
`const syncSidebarActive = () =>`,
Expand Down Expand Up @@ -434,10 +431,8 @@ func TestUIShellRendersResponsiveAccessibleSidebar(t *testing.T) {
if strings.Contains(body[menuStart:menuStart+menuEnd], "wide-only") {
t.Fatalf("member menu should remain visible when the sidebar is collapsed: %s", body)
}
legalStart := strings.Index(body, `<div data-sidebar-legal`)
memberStart := strings.Index(body, `<details data-close-on-outside`)
if legalStart < 0 || memberStart <= legalStart {
t.Fatalf("legal links must render immediately before the member profile section: %s", body)
if strings.Contains(body, `data-sidebar-legal`) || strings.Contains(body, `aria-label="Legal"`) {
t.Fatalf("shell sidebar must not render legal links: %s", body)
}
for _, roleLabel := range []string{">Member<", ">Admin<"} {
if strings.Contains(body, roleLabel) {
Expand Down Expand Up @@ -533,6 +528,11 @@ func TestUIShellRemovesCollapsedLabelsFromLoggedOutSidebar(t *testing.T) {
if strings.Contains(string(css), `.wide-only { transition: opacity`) || strings.Contains(string(css), `.wide-only { opacity: 0`) {
t.Fatalf("collapsed labels must be removed from layout instead of hidden with opacity: %s", css)
}
for _, notWant := range []string{`data-sidebar-legal`, `aria-label="Legal"`, `href="/terms"`, `href="/privacy"`, `href="/security"`} {
if strings.Contains(body, notWant) {
t.Fatalf("logged-out sidebar still contains legal link %q: %s", notWant, body)
}
}
}

func TestUIShellProvidesAccessibleIconTooltips(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions internal/server/ui_home_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ func TestUIRendersWorkSidebar(t *testing.T) {
if strings.Contains(body, `data-lucide="key-round"`) {
t.Fatalf("body still has tokens sidebar icon: %s", body)
}
for _, notWant := range []string{`data-sidebar-legal`, `aria-label="Legal"`, `href="/terms"`, `href="/privacy"`, `href="/security"`} {
if strings.Contains(body, notWant) {
t.Fatalf("work shell still contains sidebar legal link %q: %s", notWant, body)
}
}
for _, notWant := range []string{"Assigned to me", "Active work board", "Across projects"} {
if strings.Contains(body, notWant) {
t.Fatalf("body still has sidebar subtitle %q: %s", notWant, body)
Expand Down
Loading