Skip to content

Latest commit

 

History

History
119 lines (92 loc) · 24.7 KB

File metadata and controls

119 lines (92 loc) · 24.7 KB

Component Reference

Reusable server-rendered UI components live in internal/server/templates/components.html. Prefer these before adding new markup to feature templates.

Navigation

  • 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, under a small uppercase Favorites heading (hidden with the other wide-only labels when the sidebar is collapsed). 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.
  • sidebar-recents: the sidebar's Recents list below Favorites, backed by uiSidebarRecentsData. It shows the up to ten issues the signed-in user opened most recently, newest first; each row is an issue-key and a one-line title. renderUIShell loads it (store.ListRecentIssues, which drops issues the user can no longer read). The issue page and panel record each view (store.RecordIssueView); an htmx panel also swaps the list in out of band. The whole section is wide-only, so the collapsed sidebar hides it. On an issue page the issue's Recents entry, not its project, is the one active destination: the panel's data-sidebar-view element carries data-sidebar-issue-id, and app.js falls back to the project while the entry is hidden.
  • Truncation tooltips: a control with visible text can add data-tooltip plus data-tooltip-when-truncated, and mark the clipped element data-tooltip-truncates. The shared app tooltip then shows only while that text is cut off, as Recents does for long titles.
  • Account menu: the menu inside the sidebar's account footer in shell_sidebar.html ranges over accountPages (uiAccountPages, a list of uiAccountPage with View, Label, Path, and Lucide Icon) and ends with the Sign out form below a divider. Each account panel sets data-sidebar-view to its page's View, and its handler renders through renderUIAccountPage so the matching data-account-menu-link is marked current; app.js keeps that mark in step after htmx navigation and closes the menu when an item is chosen.
  • legal-links: compact links to the canonical Terms, Privacy, and Security pages. Reuse it on signed-out pages; authenticated account pages get it through account-footer, so the published documents remain consistently reachable.
  • account-footer: bordered page footer wrapping legal-links. Every account page (Profile, Login, Notifications, Tokens) ends with it; it takes no data.
  • 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.

Brand

Brand building blocks live in internal/server/templates/brand.html; see "Brand" in DESIGN_CONTEXT.md.

  • brand-head: the icon, apple-touch icon, manifest, theme colour, and stylesheet links. Every full-page document includes it inside <head>, so no page ships without the icon or the stylesheet.
  • brand-backdrop: the brand scene, a fixed, clipped, pointer-events-free layer at z-index: -1 placed directly inside <body>. Build its data with brandBackdrop <variant> <preset> <oob>: variant "" is the full, animated scene behind a single centered card (auth and OAuth pages) and "ambient" the still, calmer version behind the app shell and legal pages. Only the app shell passes the user's background preset (.Background); every other page passes "" for the default. oob re-renders it as an htmx out-of-band swap, which the Appearance card uses after a save. Any surface that sits straight on the page must be opaque.
  • brand-mark: the 28px icon and trackslash wordmark lockup for page chrome (sidebar head, mobile app bar, legal header). Wrap it in a link where the chrome needs one; it takes no data.
  • Navigation progress: shell.html renders one <div data-nav-progress class="nav-progress"> under <body>. app.js sets data-nav-busy on <html> while any htmx request targeting #main is in flight, and the stylesheet fades the bar in after a short delay.
  • statusSurface / statusCard: status tints for cards that sit straight on the page (the issue header, board cards). They lay the translucent statusRow tint over an opaque page-colour base; statusCard adds the row hover tint for linked cards. Use statusRow only for rows inside an opaque list card.

Signed-out Auth Pages

  • auth-page-open and auth-page-close in internal/server/templates/login.html: the shared document shell for login, signup, oauth-consent, and oauth-error. Pass the page's CSRF token (or "" when the page has no form) to auth-page-open. It renders brand-head, the full brand-backdrop, and opens the centered card with the icon and trackslash wordmark, so page headings inside the card are h2. Put the page body between the two templates. auth-page-close closes the card, adds legal-links, and loads auth.js, which also renders the page's Lucide icons.
  • Login password disclosure: <details data-password-login> holds the username/password form below the primary passkey button. The server renders it open when the page carries a password-login error; auth.js opens it when WebAuthn is unavailable and focuses the username field whenever it opens.

Controls

  • App tooltip: one body-level tooltip in shell_scripts.html automatically labels interactive controls that have an aria-label but no visible text. It appears on pointer hover and keyboard focus, follows the control through the shared CSS anchor in frontend/tailwind.css, and stays outside card overflow. Keep labels concise and action-oriented; do not add a tooltip to controls whose text is already visible.
  • local-time: semantic timestamp backed by uiLocalTimeData from tokenTime. It keeps canonical UTC in datetime, renders an explicit UTC fallback, and is enhanced by app.js into the browser's local date, time, and timezone on initial load and after HTMX swaps.

Badges

  • issue-key: compact monospace issue identifier badge. Use it for ticket numbers wherever possible; if a generic data-driven badge must show an issue identifier, mirror this component's monospace, uppercase, compact bordered treatment.
  • project-key: compact project key badge.
  • access-badge: icon-and-label badge for a project access setting, from a uiAccessBadge. The open or on state is tinted emerald and the restricted or off state is neutral. Build it with projectVisibilityBadge (Public globe / Private lock), projectIssueCreationBadge (Any signed-in user users / Members only user-round-check) or projectSprintModeBadge (Enabled person-standing / Disabled list-checks). Each carries its setting's data hook (data-project-visibility, data-project-issue-creation, data-project-sprint-mode) so tests and scripts can read the state without parsing copy.
  • sprint-ref: compact monospace canonical sprint-reference badge. Keep the sprint-N value lowercase and pair it with sprint titles on current, planned, and historical cards.
  • count-badge: small numeric count badge.
  • sprint-issue-count-badge: compact sprint-total badge that uses Issue for one and Issues for zero or multiple while reusing count-badge styling.
  • status-badge: issue status badge using statusClass.
  • close-reason-badge: close reason badge for closed issues.
  • missing-close-reason-badge: dashed placeholder for invalid or incomplete closed issue detail states.
  • priority-badge: circular P0-P4 priority marker.
  • tag-badge: compact hashtag badge for model.IssueTag, using DisplayName and tagClass .Color.
  • issue-due-badge: due-date badge with overdue/today/future styling.
  • issue-worker-badge: 20px square with a Lucide icon, from an issue's Worker (*model.IssueWorker) through workerBadge. It says who is meant to complete the issue: a neutral bot for an agent, an amber user-round for a human, because a human mark means an agent could not finish and someone is waiting on a person. An issue nobody has marked renders nothing. The icon is aria-hidden; the shared app tooltip (data-tooltip) and a screen-reader label read For an agent or Needs a human. It carries data-issue-worker-badge="agent|human" and isn't interactive, so it can sit inside a row link. Rows and cards put it first in their right-hand cluster, before the due badge; the issue header puts it after the priority.
  • issue-worker-label: the same mark as an icon-and-label badge (For an agent / Needs a human) for the issue Details sidebar, carrying data-issue-worker-label.
  • issue-private-badge: 20px square with a Lucide lock and the tooltip "Private", marking a private issue on rows (issue-summary-row), sprint board cards, Me and the issue header. It takes no data; render it inside {{if .Issue.Private}}. Only people who can see the issue ever get it.
  • issue-private-field: the "Keep this issue private" checkbox with its muted hint, on every form that files an issue: new issue, the help desk and the sub-issue dialog. Build it with issuePrivateField "<id>" <checked> <helpDesk>; the help-desk variant explains that the issue stays private if the project is later made public. It posts private=true.
  • issue-sprint-badge: 20px indigo square with a visible S, from a model.Sprint. It marks an issue that is in a sprint. The S is aria-hidden; the shared app tooltip (data-tooltip) and a screen-reader label both read In sprint-N · Name (status). It carries data-issue-sprint-badge="sprint-N". It isn't interactive, so it can sit inside a row link.

Avatars

  • user-avatar: circular user avatar with thumbnail-or-initials fallback. Pass userAvatar <user-like value> <class> where the value is model.User, model.ProjectMember, model.ProjectAssignee, model.ProjectAssigneeIssueStats, model.ProjectChangelogActor, or uiIssueCommentItem. The shared component owns the circular crop and clipping; callers own dimensions, colors, and borders through the class string. The helper adds cache-busting thumbnail URLs with ?v={thumbnail_object_id} and falls back to initials from display name, username, or email.
  • project-icon: square project image with a small corner radius and project-initial fallback, backed by uiProjectIconData. Pass projectIcon <project> <class>; the helper adds a cache-busting thumbnail URL and the component owns the square crop and radius.

Forms

  • csrf-field: hidden csrf_token input. Every form with method="post" must include it as {{template "csrf-field" $.CSRFToken}}, which means the data passed to that template needs a CSRFToken field populated from uiSessionCSRFToken(r). TestEveryPostFormRendersACSRFField fails the build if a posting form omits it, and TestRenderedPagesCarryAPopulatedCSRFToken fails if a construction site leaves it empty.
  • option-dropdown: expanded dropdown/listbox for choosing one option and submitting immediately. Backed by uiOptionDropdownData; use for compact enum-like changes such as issue status, close reason and worker. Options and the current value take an optional Lucide Icon/CurrentIcon.
  • autocomplete-input and autocomplete-options: shared search/autofill building blocks backed by uiAutocompleteEditData and uiAutocompleteOption. Supports local option filtering, optional hidden target values, addressable option containers, collapsible suggestions, and optional debounced HTMX refresh on input for server-filtered suggestions.
  • autocomplete-edit: search-style edit row with suggestions and save/cancel actions. Use for member, sprint, and similar lookup fields.

Modals

  • modal-open and modal-close: reusable modal shell with title, optional description, badges, and cancel action. Wrap workflow-specific body content between the two templates. Client-controlled modals may set Open when a validation response must keep the modal visible; the shared script focuses the first form control after open or an HTMX swap.
  • Add/create controls on management pages open this shared modal instead of permanently rendering a second form beside existing content. Dedicated creation pages remain full-page workflows; Context remains the documented integrated-manager exception.
  • image-picker: shared client-controlled profile/project image modal backed by uiImagePickerData. Keep only the current avatar/icon and its Add/Change trigger in the owning panel; file selection, upload, and removal live inside this modal. Profile previews remain circular and project previews remain square with a small corner radius.
  • Issue-scoped relationship edits should prefer modals when the user is making a small local change from issue detail. Issue Context is the deliberate exception because browsing and editing multiple documents benefits from its integrated manager. Other modal workflows should keep the surrounding issue visible, avoid URL pushes for open/submit/close, support repeated HTMX updates, and link out when the task expands.
  • Issue tag modal convention: show attached tags first, then a searchable list of available project tags. Attach/detach existing tags only; create/edit/delete project tags stays in the project tag manager.

Rows And Notices

  • issue-summary-row: responsive issue list row content accepting a uiIssueItem. It stacks key/priority, title/tags, and due/status metadata on mobile, then restores the compact four-column row from sm upward. When SubIssueProgress.Total is non-zero, it also shows the shared compact completed/total ring used on sprint cards. It shows issue-worker-badge before the due badge when the issue is marked. When SprintBadge is set, it shows issue-sprint-badge after the due badge. Only lists that are not already grouped by sprint set it.
  • issue-delete-notice: restore notice shown after deleting an issue.
  • Shell responses: renderUIShell renders the whole document for a navigation and only shell-main-content for an htmx request. Every htmx control targets a sub-element, and #main is a sibling of the sidebar inside .app-shell, so answering htmx with a document swaps a second header, sidebar, and #main into the existing #main. Add new whole-page panels to shell-main-content, never to the shell-main wrapper.
  • error-panel: shell-hosted error page backed by uiErrorPanelData (status, title, message), with copy from uiErrorPageFor. Rendered through renderUIShell so signed-in visitors keep their sidebar. Unmatched URLs get it from uiNotFound. In the signed-in route group, the uiErrorPages middleware turns any plain-text 4xx/5xx (writeUIStoreError, http.Error) answered to a browser page navigation (a GET that accepts text/html and isn't htmx) into this page with the same status; htmx, fetch, image, and websocket requests keep the plain-text body. Add new whole-page error states here rather than hand-rolling error markup.
  • Context detail row: issue detail uses a Details-sidebar row labeled Context, a count-badge, and a compact book-open action that opens the integrated issue Context manager and pushes its URL. Project About does not render context; project context is a top-level tab.

Feature Panels

  • project-favorite-action: project header star toggle backed by uiProjectFavoriteData/uiProjectPanelData. Keep it adjacent to the project title and update only the action wrapper plus sidebar-favorites.
  • project-panel-context: integrated project Context tab in internal/server/templates/project_panel_context.html; expects uiContextManagerData through uiProjectPanelData.ContextManager. It owns the ordered page list, selected Markdown page, and complete linked-issue list in a separate section below the document card.
  • Tokens page: API tokens keep a per-row list with individual revoke; web sessions collapse to a live count and one Revoke all web sessions action. Sessions are numerous and their names carry no information, so a row each buried the tokens people actually manage. The action revokes the caller's own session too and signs them out.
  • Tokens page, Connectors section: registered OAuth clients keep a per-row list with individual revoke, between API tokens and web sessions. Connector access tokens collapse to a live count for the same reason sessions do — they are reissued hourly by the connector itself. Registration uses the shared modal-open/modal-close modal through oauthClientCreateModal, which follows the API token modal's create-then-reveal-once convention: ClientControlled with Open set when there is an error to show or a secret to reveal. The client secret is hashed at rest, so the modal is the only place it is ever displayed.
  • oauth-consent and oauth-error in internal/server/templates/oauth_consent.html: standalone documents backed by uiOAuthConsentData and uiOAuthErrorData, built on the auth-page-open/auth-page-close sign-in card rather than rendered through renderUIShell. They are interstitials shown on behalf of a third party, not pages of the product, so they deliberately carry no sidebar or shell chrome. See OAUTH.md.
  • project-delete-modal: destructive project deletion dialog in internal/server/templates/project_panel.html, backed by projectDeleteModal and the DeleteProject* fields on uiProjectPanelData. The project actions menu links to /{owner}/projects/{key}/delete, which re-renders the panel with the dialog open; the same URL performs the deletion on POST. It states the consequences, requires the project key to be typed, and is only rendered when CanDeleteProject is set. Destructive actions this broad get a typed confirmation rather than hx-confirm, which stays right for single-row deletes.
  • range-control in components.html: a segmented row of links that reloads the current project view over a different window, backed by uiRangeControl/uiRangeOption and built in templates with rangeControl "<aria label>" .Options. The active option carries aria-current="true". Insights uses it for its date range and In progress for its completion window; reuse it rather than drawing another segmented control.
  • project-panel-progress in internal/server/templates/project_panel_progress.html: the In progress view shown instead of Planned when sprints are off, backed by uiProjectProgressData (built in ui_project_progress.go from the shared projectProgress helper that also serves the API and MCP). It renders the In progress and Recently completed sections with the shared issue-list; recently completed items set uiIssueItem.CompletedAt, which issue-summary-row shows before the status badge. ProgressNotice on the panel shows a one-line explanation above both sections.
  • project-panel-insights and insight-chart in internal/server/templates/project_panel_insights.html: the project Insights view and its reusable chart card, backed by uiProjectInsightsData and uiInsightChart (built in ui_insight_charts.go). A chart card owns its title, description, stat row, legend toggles, plot, notes, and Data table disclosure. Kinds are line, area (stacked), bars (grouped, one column per period), and scatter. Lines and areas draw in a stretched 0 0 1000 1000 viewBox with non-scaling strokes; text, dots, bars, and gridlines use percentage coordinates so they keep their shape at any width. The server embeds tooltip data as JSON in data-insight-chart, and app.js adds the crosshair, per-series hover markers, tooltips (from the data-insight-tooltip-* templates, filled with textContent), arrow-key stepping, tap-to-inspect, and legend toggles. Nothing writes inline scripts or styles, which the CSP forbids: the tooltip lives in an SVG foreignObject that moves by its x/y attributes. Set Empty for an empty state instead of drawing an empty plot.
  • helpdesk-panel: a help-desk reporter's whole view of a project in internal/server/templates/helpdesk.html, from uiHelpDeskPanelData (View is new, issues or issue). It must never carry anything a reporter may not see: build it from model.ReporterIssue and shared comments only, render Markdown without attachment targets, and name comment authors by display name, never email. The new-issue form ends with the one sanctioned trackslash promotion line (data-helpdesk-promo); keep it a muted footer note, not a banner or card.
  • project-member-page: full project access manager in internal/server/templates/project_member_page.html; expects member, access-mode (AccessSettings.AccessMode), sprint-mode (Project.SprintsEnabled, SprintModeLocked, SprintModeError), and blocked-user fields on uiProjectPanelData. Keep the owner fixed, use avatar/name/username rows with inline role selectors, offer the access modes as one radio group from projectAccessModeOptions (never separate switches that can combine into a mode that does not exist), keep the Sprints setting in its own form beside Access and lock it while a sprint is active, require an exact username when blocking, and re-render the page after each mutation.
  • project-panel-whiteboard: integrated project Whiteboard tab in internal/server/templates/project_panel_whiteboard.html; expects uiWhiteboardData through uiProjectPanelData.Whiteboard, built by uiBuildProjectPanel so the project header keeps every permitted action. It owns the most-recently-updated-first title list and one selected, created, or edited Markdown page, reusing description-editor (without upload configuration) and description-body. It never renders issue-linking or attachment UI.
  • context-manager-panel: routes issue mode to the integrated list/document manager in internal/server/templates/issue_context_manager.html; project mode remains a compatibility fallback because project Context normally renders through project-panel-context.
  • description-body: shared safe Markdown display backed by uiDescriptionBodyData. Project, issue, and sprint adapters pass attachment-scoped rendered HTML.
  • description-editor: shared Markdown textarea backed by uiDescriptionEditorData, with optional upload and attachment-list URLs. Creation forms omit upload configuration until a parent ref exists.
  • description-attachment-list: shared project/issue/sprint attachment rows backed by uiAttachmentListData, including previews, metadata, Markdown copy, download, delete, pagination notice, and editing state.
  • sprint-description: shared active/planned/completed-history sprint cropped-Markdown preview backed by uiSprintDescriptionData or the matching fields on uiPlannedSprint. It lazily swaps full Markdown and attachment rows through See more without affecting the sprint-issues disclosure.

Context Page Conventions

  • Project tab route: /{owner}/projects/{key}/context; selected pages use /context/{contextRef}. Issue manager route: /{owner}/issues/{issueRef}/context, with the same selected-page suffix.
  • Project pages support create/import/edit/delete, page-scoped attachments, ordering, and linked-issue management. The page list stays compact without per-page issue counts; only the selected page renders content, followed by its complete linked-issue list and count.
  • Markdown pages use the shared safe Markdown renderer and attachment components. Plain-text imports remain escaped and pre-wrapped.
  • Issue manager mode supports creating and editing issue-scoped context, attaching and viewing project pages read-only, and removing links in the same responsive list/document pattern as project Context.
  • User-facing attach/search controls use context titles. Do not present refs such as context-1 as visible identifiers, badges, placeholders, or option labels.

Whiteboard Page Conventions

  • Project tab route: /{owner}/projects/{key}/whiteboard; selected pages use /whiteboard/{whiteboard-N}, with /new, /{ref}/edit, and /{ref}/delete for writers.
  • Pages are title plus Markdown only. They render through the shared safe Markdown pipeline with no attachment store, so external images stay inert links and object-N refs stay text.
  • Delete uses a single hx-confirm action. Refs such as whiteboard-1 stay in URLs and API/MCP mechanics, never as visible row labels.

When adding a reusable component, document its template name, purpose, and expected data shape here.