Skip to content

Commit 4bfcd5e

Browse files
cpcloudcursoragent
andcommitted
refactor(ui): show green $ in money column headers, bare values in cells
Money columns now always display a green "$" in the header and bare numbers (no $ prefix) in cell values. FormatCentsBare formats cents without the dollar sign for table cells; FormatCents (with $) is still used in forms and the dashboard spending line. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ee0fb59 commit 4bfcd5e

File tree

9 files changed

+57
-37
lines changed

9 files changed

+57
-37
lines changed

internal/app/detail_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ func newTestModelWithDetailRows() *Model {
254254
// Seed a couple rows.
255255
tab.Table.SetRows([]table.Row{
256256
{"1", "2026-01-15", "Self", "", "first"},
257-
{"2", "2026-02-01", "Acme", "$150.00", "second"},
257+
{"2", "2026-02-01", "Acme", "150.00", "second"},
258258
})
259259
tab.Rows = []rowMeta{{ID: 1}, {ID: 2}}
260260
tab.CellRows = [][]cell{
@@ -269,7 +269,7 @@ func newTestModelWithDetailRows() *Model {
269269
{Value: "2", Kind: cellReadonly},
270270
{Value: "2026-02-01", Kind: cellDate},
271271
{Value: "Acme", Kind: cellText},
272-
{Value: "$150.00", Kind: cellMoney},
272+
{Value: "150.00", Kind: cellMoney},
273273
{Value: "second", Kind: cellText},
274274
},
275275
}

internal/app/mag.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ func magTransformCells(rows [][]cell) [][]cell {
100100
return out
101101
}
102102

103-
// magAnnotateSpecs returns a copy of specs with a styled "$" suffix on
104-
// money column titles so the unit is visible in the header instead of
105-
// repeated in every cell.
106-
func magAnnotateSpecs(specs []columnSpec, styles Styles) []columnSpec {
103+
// annotateMoneyHeaders returns a copy of specs with a styled "$" suffix
104+
// on money column titles. The unit lives in the header so cell values
105+
// can be bare numbers.
106+
func annotateMoneyHeaders(specs []columnSpec, styles Styles) []columnSpec {
107107
out := make([]columnSpec, len(specs))
108108
copy(out, specs)
109109
for i, spec := range out {

internal/app/mag_test.go

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
)
1212

1313
func TestMagFormatMoneyWithUnit(t *testing.T) {
14+
// Used by magCents for dashboard (input still has $ from FormatCents).
1415
tests := []struct {
1516
name string
1617
value string
@@ -31,21 +32,21 @@ func TestMagFormatMoneyWithUnit(t *testing.T) {
3132
}
3233
}
3334

34-
func TestMagFormatMoneyWithoutUnit(t *testing.T) {
35+
func TestMagFormatBareMoney(t *testing.T) {
36+
// Table cells use FormatCentsBare (no $ prefix).
3537
tests := []struct {
3638
name string
3739
value string
3840
want string
3941
}{
40-
{"thousands", "$5,234.23", "\U0001F8213"},
41-
{"hundreds", "$500.00", "\U0001F8212"},
42-
{"millions", "$1,000,000.00", "\U0001F8216"},
43-
{"tens", "$42.00", "\U0001F8211"},
44-
{"single digit", "$7.50", "\U0001F8210"},
45-
{"sub-dollar", "$0.50", "\U0001F821-1"},
46-
{"zero", "$0.00", "\U0001F8210"},
47-
{"negative", "-$5.00", "-\U0001F8210"},
48-
{"negative large", "-$12,345.00", "-\U0001F8214"},
42+
{"thousands", "5,234.23", "\U0001F8213"},
43+
{"hundreds", "500.00", "\U0001F8212"},
44+
{"millions", "1,000,000.00", "\U0001F8216"},
45+
{"tens", "42.00", "\U0001F8211"},
46+
{"single digit", "7.50", "\U0001F8210"},
47+
{"sub-dollar", "0.50", "\U0001F821-1"},
48+
{"zero", "0.00", "\U0001F8210"},
49+
{"negative", "-5.00", "-\U0001F8210"},
4950
}
5051
for _, tt := range tests {
5152
t.Run(tt.name, func(t *testing.T) {
@@ -102,18 +103,18 @@ func TestMagFormatSkipsNonNumeric(t *testing.T) {
102103
}
103104
}
104105

105-
func TestMagTransformCellsStripsUnit(t *testing.T) {
106+
func TestMagTransformCells(t *testing.T) {
106107
rows := [][]cell{
107108
{
108109
{Value: "1", Kind: cellReadonly},
109110
{Value: "Kitchen Remodel", Kind: cellText},
110-
{Value: "$5,234.23", Kind: cellMoney},
111+
{Value: "5,234.23", Kind: cellMoney},
111112
{Value: "3", Kind: cellDrilldown},
112113
},
113114
{
114115
{Value: "2", Kind: cellReadonly},
115116
{Value: "Deck", Kind: cellText},
116-
{Value: "$100.00", Kind: cellMoney},
117+
{Value: "100.00", Kind: cellMoney},
117118
{Value: "0", Kind: cellDrilldown},
118119
},
119120
}
@@ -127,7 +128,7 @@ func TestMagTransformCellsStripsUnit(t *testing.T) {
127128
assert.Equal(t, "Kitchen Remodel", out[0][1].Value)
128129
assert.Equal(t, "Deck", out[1][1].Value)
129130

130-
// Money cells: magnitude only, no $ prefix.
131+
// Money cells: magnitude only.
131132
assert.Equal(t, "\U0001F8213", out[0][2].Value)
132133
assert.Equal(t, "\U0001F8212", out[1][2].Value)
133134

@@ -136,18 +137,18 @@ func TestMagTransformCellsStripsUnit(t *testing.T) {
136137
assert.Equal(t, "\U0001F8210", out[1][3].Value)
137138

138139
// Original rows are not modified.
139-
assert.Equal(t, "$5,234.23", rows[0][2].Value)
140+
assert.Equal(t, "5,234.23", rows[0][2].Value)
140141
}
141142

142-
func TestMagAnnotateSpecs(t *testing.T) {
143+
func TestAnnotateMoneyHeaders(t *testing.T) {
143144
styles := DefaultStyles()
144145
specs := []columnSpec{
145146
{Title: "Name", Kind: cellText},
146147
{Title: "Total", Kind: cellMoney},
147148
{Title: "Labor", Kind: cellMoney},
148149
{Title: "ID", Kind: cellReadonly},
149150
}
150-
out := magAnnotateSpecs(specs, styles)
151+
out := annotateMoneyHeaders(specs, styles)
151152

152153
// Non-money columns unchanged.
153154
assert.Equal(t, "Name", out[0].Title)
@@ -163,13 +164,13 @@ func TestMagAnnotateSpecs(t *testing.T) {
163164
assert.Equal(t, "Total", specs[1].Title)
164165
}
165166

166-
func TestMagAnnotateSpecsPreservesLength(t *testing.T) {
167+
func TestAnnotateMoneyHeadersPreservesLength(t *testing.T) {
167168
styles := DefaultStyles()
168169
specs := []columnSpec{
169170
{Title: "A", Kind: cellText},
170171
{Title: "B", Kind: cellMoney},
171172
}
172-
out := magAnnotateSpecs(specs, styles)
173+
out := annotateMoneyHeaders(specs, styles)
173174
require.Len(t, out, 2)
174175
}
175176

internal/app/notes_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestNotePreviewOpensOnEnter(t *testing.T) {
2222
// Seed a row with a note.
2323
tab.Table.SetRows(
2424
[]table.Row{
25-
{"1", "2026-01-15", "Self", "$50.00", "Changed the filter and checked pressure"},
25+
{"1", "2026-01-15", "Self", "50.00", "Changed the filter and checked pressure"},
2626
},
2727
)
2828
tab.Rows = []rowMeta{{ID: 1}}
@@ -31,7 +31,7 @@ func TestNotePreviewOpensOnEnter(t *testing.T) {
3131
{Value: "1", Kind: cellReadonly},
3232
{Value: "2026-01-15", Kind: cellDate},
3333
{Value: "Self", Kind: cellText},
34-
{Value: "$50.00", Kind: cellMoney},
34+
{Value: "50.00", Kind: cellMoney},
3535
{Value: "Changed the filter and checked pressure", Kind: cellNotes},
3636
},
3737
}

internal/app/rows_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestDateValue(t *testing.T) {
2323
func TestCentsValue(t *testing.T) {
2424
assert.Empty(t, centsValue(nil))
2525
c := int64(123456)
26-
assert.Equal(t, "$1,234.56", centsValue(&c))
26+
assert.Equal(t, "1,234.56", centsValue(&c))
2727
}
2828

2929
func TestProjectRows(t *testing.T) {
@@ -45,7 +45,7 @@ func TestProjectRows(t *testing.T) {
4545
assert.Equal(t, uint(1), meta[0].ID)
4646
assert.False(t, meta[0].Deleted)
4747
assert.Equal(t, "Kitchen", cells[0][2].Value)
48-
assert.Equal(t, "$1,000.00", cells[0][4].Value)
48+
assert.Equal(t, "1,000.00", cells[0][4].Value)
4949
assert.Equal(t, "2025-03-01", cells[0][6].Value)
5050
assert.Equal(t, "Kitchen", rows[0][2])
5151
}
@@ -81,7 +81,7 @@ func TestQuoteRows(t *testing.T) {
8181
assert.Equal(t, "Kitchen", cells[0][1].Value)
8282
assert.Equal(t, uint(1), cells[0][1].LinkID)
8383
assert.Equal(t, "ContractorCo", cells[0][2].Value)
84-
assert.Equal(t, "$500.00", cells[0][3].Value)
84+
assert.Equal(t, "500.00", cells[0][3].Value)
8585
}
8686

8787
func TestQuoteRowsFallbackProjectName(t *testing.T) {
@@ -153,7 +153,7 @@ func TestApplianceRows(t *testing.T) {
153153
assert.Equal(t, "Samsung", cells[0][2].Value)
154154
assert.Equal(t, "2023-06-15", cells[0][6].Value)
155155
assert.Equal(t, "2y", cells[0][7].Value)
156-
assert.Equal(t, "$899.00", cells[0][9].Value)
156+
assert.Equal(t, "899.00", cells[0][9].Value)
157157
assert.Equal(t, "2", cells[0][10].Value)
158158
}
159159

internal/app/sort_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ func newSortTab() *Tab {
2323
{
2424
{Value: "3", Kind: cellReadonly},
2525
{Value: "Charlie", Kind: cellText},
26-
{Value: "$200.00", Kind: cellMoney},
26+
{Value: "200.00", Kind: cellMoney},
2727
{Value: "2025-03-01", Kind: cellDate},
2828
},
2929
{
3030
{Value: "1", Kind: cellReadonly},
3131
{Value: "Alice", Kind: cellText},
32-
{Value: "$50.00", Kind: cellMoney},
32+
{Value: "50.00", Kind: cellMoney},
3333
{Value: "2025-01-15", Kind: cellDate},
3434
},
3535
{
3636
{Value: "2", Kind: cellReadonly},
3737
{Value: "Bob", Kind: cellText},
38-
{Value: "$1,000.00", Kind: cellMoney},
38+
{Value: "1,000.00", Kind: cellMoney},
3939
{Value: "2025-02-10", Kind: cellDate},
4040
},
4141
},
@@ -123,7 +123,7 @@ func TestApplySortsByMoneyAsc(t *testing.T) {
123123
applySorts(tab)
124124

125125
costs := collectCol(tab, 2)
126-
assert.Equal(t, []string{"$50.00", "$200.00", "$1,000.00"}, costs)
126+
assert.Equal(t, []string{"50.00", "200.00", "1,000.00"}, costs)
127127
}
128128

129129
func TestApplySortsByDateDesc(t *testing.T) {

internal/app/tables.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ func quoteRows(
434434
{Value: fmt.Sprintf("%d", q.ID), Kind: cellReadonly},
435435
{Value: projectName, Kind: cellText, LinkID: q.ProjectID},
436436
{Value: q.Vendor.Name, Kind: cellText, LinkID: q.VendorID},
437-
{Value: data.FormatCents(q.TotalCents), Kind: cellMoney},
437+
{Value: data.FormatCentsBare(q.TotalCents), Kind: cellMoney},
438438
{Value: centsValue(q.LaborCents), Kind: cellMoney},
439439
{Value: centsValue(q.MaterialsCents), Kind: cellMoney},
440440
{Value: centsValue(q.OtherCents), Kind: cellMoney},
@@ -628,7 +628,7 @@ func centsValue(cents *int64) string {
628628
if cents == nil {
629629
return ""
630630
}
631-
return data.FormatCents(*cents)
631+
return data.FormatCentsBare(*cents)
632632
}
633633

634634
func dateValue(value *time.Time) string {

internal/data/validation.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ func FormatCents(cents int64) string {
5353
return fmt.Sprintf("%s$%s.%02d", sign, humanize.Comma(dollars), remainder)
5454
}
5555

56+
// FormatCentsBare formats cents without the dollar sign (e.g. "1,234.56").
57+
// Used for table cells where the column header carries the unit.
58+
func FormatCentsBare(cents int64) string {
59+
sign := ""
60+
if cents < 0 {
61+
sign = "-"
62+
cents = -cents
63+
}
64+
dollars := cents / 100
65+
remainder := cents % 100
66+
return fmt.Sprintf("%s%s.%02d", sign, formatWithCommas(dollars), remainder)
67+
}
68+
5669
func FormatOptionalCents(cents *int64) string {
5770
if cents == nil {
5871
return ""

internal/data/validation_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ func TestFormatCents(t *testing.T) {
5252
assert.Equal(t, "$1,234.56", FormatCents(123456))
5353
}
5454

55+
func TestFormatCentsBare(t *testing.T) {
56+
assert.Equal(t, "1,234.56", FormatCentsBare(123456))
57+
assert.Equal(t, "-5.00", FormatCentsBare(-500))
58+
assert.Equal(t, "0.00", FormatCentsBare(0))
59+
}
60+
5561
func TestParseOptionalDate(t *testing.T) {
5662
date, err := ParseOptionalDate("2025-06-11")
5763
require.NoError(t, err)

0 commit comments

Comments
 (0)