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
50 changes: 31 additions & 19 deletions internal/templates/components/pages/rule_detail.templ
Original file line number Diff line number Diff line change
Expand Up @@ -354,31 +354,21 @@ templ ruleDetailRecentApplications(p RuleDetailProps) {
</div>
<p class="text-xs text-base-content/40 mb-3">Transactions this rule has touched during syncs</p>
if len(p.ApplicationTxns) > 0 {
<!-- One card wraps the stream: per-application action strip + full tx-row.
Each application is grouped in a divider-separated block so scanning
top-to-bottom reads as "this is what the rule did, on this transaction". -->
<!-- One card wraps the stream: a divider-separated full tx-row per
application. How the row got here ("Retroactive") rides inline in
the row's meta line rather than as a floating top-right label —
the rule's action itself is already described in the "Then" card
above, so it isn't repeated per row. -->
<div class="bb-card overflow-hidden">
<div class="divide-y divide-base-300/50">
for _, tx := range p.ApplicationTxns {
{{ meta := p.ApplicationMeta[tx.ID] }}
<div>
if meta.AppliedBy != "sync" {
<!-- The rule's action is already described in the "Then" card above,
so don't repeat it per row. Only surface how the row got here
when it wasn't the default ("via sync"). -->
<div class="flex items-center justify-end px-4 pt-2 text-[0.65rem] uppercase tracking-wider text-base-content/40">
<span class="inline-flex items-center gap-1">
if meta.AppliedBy == "retroactive" {
@components.LucideIcon("rewind", "w-3 h-3")
retroactive
} else {
@components.LucideIcon("wrench", "w-3 h-3")
{ meta.AppliedBy }
}
</span>
</div>
if meta.AppliedBy != "" && meta.AppliedBy != "sync" {
@components.TxRow(tx, components.WithMetaBadge(ruleAppliedByBadge(meta.AppliedBy)))
} else {
@components.TxRow(tx)
}
@components.TxRow(tx)
</div>
}
</div>
Expand All @@ -394,6 +384,28 @@ templ ruleDetailRecentApplications(p RuleDetailProps) {
</div>
}

// ruleAppliedByBadge renders a compact inline meta chip describing how a
// Recent-Applications row was touched by this rule. It rides in the TxRow
// meta (subtitle) line via components.WithMetaBadge, replacing the old
// floating top-right "RETROACTIVE" strip (#1917). Only non-"sync" origins
// surface a badge — sync is the default firing path and stays unlabeled.
// Styling matches the row's other meta chips (muted icon + text) so it reads
// as just another subtitle detail rather than a separate banner.
templ ruleAppliedByBadge(appliedBy string) {
<span class="inline-flex items-center gap-1 text-base-content/40 shrink-0" title={ ruleAppliedByTitle(appliedBy) }>
if appliedBy == "retroactive" {
@components.LucideIcon("rewind", "w-3 h-3")
<!-- Label hides below sm where the row meta is cramped; the icon +
title carry the meaning there. The desktop meta line is itself
sm+ only, so it always shows the full label. -->
<span class="hidden sm:inline">Retroactive</span>
} else {
@components.LucideIcon("wrench", "w-3 h-3")
<span class="hidden sm:inline">{ appliedBy }</span>
}
</span>
}

// ruleDetailPendingMatches renders the "Matching transactions" card — only
// rendered when preview data is available.
//
Expand Down
10 changes: 10 additions & 0 deletions internal/templates/components/pages/rule_detail_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ type RuleApplicationMeta struct {
AppliedBy string
}

// ruleAppliedByTitle returns the tooltip text for the inline meta badge that
// marks how a Recent-Applications row was touched. Retroactive is the common
// case; any other non-sync origin falls back to a generic phrasing.
func ruleAppliedByTitle(appliedBy string) string {
if appliedBy == "retroactive" {
return "Applied retroactively from this rule's detail page"
}
return "Applied via " + appliedBy
}

// ruleID safely returns the rule's ID, or empty string when the rule
// pointer is nil. Used to build the data-apply-url / data-toggle-url
// attributes without crashing the template render on a transient nil.
Expand Down
51 changes: 50 additions & 1 deletion internal/templates/components/tx_row.templ
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ import (
//
// The inline category picker still reads window.__bbCategories from the
// page; pages that render tx rows must continue to populate that global.
templ TxRow(tx service.AdminTransactionRow) {
//
// Optional TxRowOption values let a page embellish the row without forcing
// the ~50 existing call sites to thread a props struct. The only option
// today is WithMetaBadge, which renders a small chip at the tail of the
// meta (subtitle) line — the rule-detail "Recent Applications" list uses it
// to mark how a row was touched (e.g. "Retroactive") inline, instead of
// floating a separate label above the row.
templ TxRow(tx service.AdminTransactionRow, opts ...TxRowOption) {
{{ cfg := buildTxRowConfig(opts) }}
<div
class="bb-tx-row group"
x-data="{ expanded: false }"
Expand Down Expand Up @@ -133,6 +141,13 @@ templ TxRow(tx service.AdminTransactionRow) {
@LucideIcon("flag", "w-3 h-3")
</span>
}
<!-- Slot 6 — optional caller-supplied meta badge (e.g. the
rule-detail "Retroactive" marker). Self-contained so the
leading separator vanishes with it when no badge is set. -->
if cfg.MetaBadge != nil {
<span class="text-base-content/20">&middot;</span>
@cfg.MetaBadge
}
</div>
<!-- Mobile-only sub-line (<sm): date + category dot.
Keeps the row informative on iPhone without crowding the name.
Expand Down Expand Up @@ -170,6 +185,10 @@ templ TxRow(tx service.AdminTransactionRow) {
<span class="text-[0.65rem] tabular-nums font-medium">{ strconv.Itoa(tx.CommentCount) }</span>
</span>
}
if cfg.MetaBadge != nil {
<span class="text-base-content/20 shrink-0">&middot;</span>
@cfg.MetaBadge
}
</div>
</div>
<!-- Inline category picker (xl+). Below xl the compact label above
Expand Down Expand Up @@ -336,6 +355,36 @@ func txAvatarColorStyle(color *string) string {
return "--avatar-color: " + txNeutralCategoryColor
}

// TxRowOption configures optional, page-specific embellishments on a TxRow
// without forcing the ~50 existing call sites to thread a props struct.
type TxRowOption func(*txRowConfig)

// txRowConfig holds resolved TxRow options.
type txRowConfig struct {
// MetaBadge, when non-nil, renders inline at the tail of the row's meta
// (subtitle) line.
MetaBadge templ.Component
}

// WithMetaBadge injects a small inline chip at the end of the meta line.
// Used by the rule-detail "Recent Applications" list to mark how a row was
// touched (e.g. "Retroactive") without floating a separate label above it.
func WithMetaBadge(c templ.Component) TxRowOption {
return func(cfg *txRowConfig) { cfg.MetaBadge = c }
}

// buildTxRowConfig folds the variadic options into a config, tolerating nil
// options so callers can pass WithMetaBadge(maybeNil) without branching.
func buildTxRowConfig(opts []TxRowOption) txRowConfig {
var cfg txRowConfig
for _, opt := range opts {
if opt != nil {
opt(&cfg)
}
}
return cfg
}

// txCategoryInitial returns the category UUID for the picker's data-initial
// attribute, or empty string when the row is uncategorized. The shared
// `categoryPicker` factory reads this via `data-initial` and pulls the
Expand Down
Loading