Documentation
¶
Overview ¶
Package comments owns the goldmark+bluemonday pipeline that turns operator-authored markdown into the sanitized HTML cached in the comments table. Renderer instances are safe for concurrent use; the package exposes a default instance via Default() for the daemon's hot path.
Allowed markdown subset (per v1.8 issue scope):
- bold + italic + inline code + code fences
- bulleted + numbered lists
- links (with rel="noopener noreferrer" + target="_blank")
- headings (h1–h3)
- blockquotes
Disallowed:
- raw HTML (stripped by bluemonday before rendering)
- images (potential pixel-tracking, off-domain fetches)
- tables / footnotes (kept out at v1.8; revisit if operators ask)
- scripts / styles (stripped by bluemonday)
Index ¶
- Constants
- Variables
- func ExtractMentions(body string) []string
- func SetClock(fn func() time.Time) func() time.Time
- type AddOptions
- type Comment
- type Renderer
- type Repo
- func (r *Repo) Add(ctx context.Context, fingerprint, authorID, body string, opts AddOptions) (string, error)
- func (r *Repo) ByExternalID(ctx context.Context, source, externalID string) (Comment, error)
- func (r *Repo) ByID(ctx context.Context, id string) (Comment, error)
- func (r *Repo) CountByFingerprint(ctx context.Context, fingerprint string) (int, error)
- func (r *Repo) Delete(ctx context.Context, id string) error
- func (r *Repo) Edit(ctx context.Context, id, body string) error
- func (r *Repo) ListByFingerprint(ctx context.Context, fingerprint string) ([]Comment, error)
- func (r *Repo) WithRenderer(rr *Renderer) *Repo
Constants ¶
const ( SourceUI = "ui" SourceSlack = "slack" SourceTeams = "teams" SourceGitHubPR = "github-pr" SourceJira = "jira" SourceLinear = "linear" )
Source values flow into the comments.source column. The allowed set matches the CHECK constraint in migration 0008.
Variables ¶
var ErrEmptyBody = errors.New("comment body is empty")
ErrEmptyBody is returned by Add and Edit when the markdown source is empty after trimming. The UI surfaces this as a flash.
Functions ¶
func ExtractMentions ¶
ExtractMentions returns the unique @handles referenced in body, preserving insertion order. The handle is everything between the "@" and the first non-handle character — typically a username, email local-part, or "team-<slug>".
The pattern is conservative: a token starting with a dot or dash is dropped, and minimum length is 2 characters so common false positives ("@", "@.", "@@") drop out.
Types ¶
type AddOptions ¶
AddOptions is the optional non-UI metadata bag for Add. The UI path leaves this zero-valued; sink ingest paths populate Source + ExternalID so two-way sync can dedup re-delivery.
type Comment ¶
type Comment struct {
ID string
FindingFingerprint string
AuthorID string
AuthorEmail string
AuthorDisplayName string
Body string
BodyHTML string
CreatedAt time.Time
UpdatedAt time.Time
EditedAt *time.Time
Source string
ExternalID string
}
Comment is the per-row shape held by the daemon and exposed to the UI templates. AuthorEmail / AuthorDisplayName resolve out of the users table via JOIN at read time; comments table itself only stores the foreign key.
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer wraps goldmark + bluemonday with a fixed allowlist policy. Reusable across requests; no per-render state escapes.
func Default ¶
func Default() *Renderer
Default returns the package-global renderer, lazily constructed on first use. Daemon handlers should call this rather than allocating their own Renderer per request.
func (*Renderer) Render ¶
Render converts markdown source into sanitized HTML. The output is safe to embed in an HTML response without further escaping.
Empty input returns an empty string; invalid markdown still renders (goldmark is lenient) — bluemonday is the final gate that strips anything outside the allowlist.
type Repo ¶
type Repo struct {
// contains filtered or unexported fields
}
Repo is the persistence layer over the v1.8 comments table.
func NewRepo ¶
NewRepo wires a Repo against the given store. The default Renderer is used for markdown→HTML conversion.
func (*Repo) Add ¶
func (r *Repo) Add(ctx context.Context, fingerprint, authorID, body string, opts AddOptions) (string, error)
Add inserts a comment authored by `authorID` against the finding identified by `fingerprint`. Returns the new row's ID.
`body` is the markdown source as the operator typed it. The renderer produces body_html in the same write so list rendering stays cheap. ErrEmptyBody is returned if `body` trims to "".
func (*Repo) ByExternalID ¶
ByExternalID resolves a comment via its sink-native identifier. Used by inbound two-way sync paths to detect re-delivery.
func (*Repo) CountByFingerprint ¶
CountByFingerprint returns how many comments are attached to the fingerprint. Drives the badge on the finding row.
func (*Repo) Edit ¶
Edit rewrites the comment body. EditedAt is stamped on first edit; subsequent edits keep the original EditedAt? No — every edit updates EditedAt so "edited Nm ago" tracks the latest write. Returns ErrEmptyBody for empty trimmed bodies.
func (*Repo) ListByFingerprint ¶
ListByFingerprint returns the ordered conversation attached to the given fingerprint, oldest-first. The UI renders bottom-to-top or top-to-bottom from the same slice.
func (*Repo) WithRenderer ¶
WithRenderer overrides the markdown renderer. Useful for tests that want to assert specific HTML output or skip rendering.