Skip to content

cli: support for new layout bare-wide - #2691

Merged
simonmichael merged 11 commits into
hledgerorg:mainfrom
thielema:layout-bare-wide
Sep 10, 2026
Merged

cli: support for new layout bare-wide#2691
simonmichael merged 11 commits into
hledgerorg:mainfrom
thielema:layout-bare-wide

Conversation

@thielema

Copy link
Copy Markdown
Contributor

Re-request of #2253 now against main instead of master.

@thielema

Copy link
Copy Markdown
Contributor Author

What concerns hold back this pull request?

@simonmichael

Copy link
Copy Markdown
Member

Hi, none in particular - I probably skipped it because it's marked Draft.

I have looked at it now. It's nice!

But I think I see a bad interaction with -T and -A columns, their values don't look right ?

And, it's not super clear if you combine --layout bare-wide with multiple periods - no subperiod headings are visible. But that needn't be a blocker.

Any other names you considered ? Do you think we should drop the hyphen for easier typing ?

I guess there is symmetry, not quite reflected in the names:

integrated symbols separated symbols
tall bare
wide bare-wide
tidy (csv only)

@simonmichael simonmichael added A-WISH Some kind of improvement request or proposal. web The hledger-web tool. balance balancesheet incomestatement cashflow balancesheetequity needs-discussion To unblock: needs more discussion/review/exploration and removed web The hledger-web tool. labels Aug 21, 2026
@simonmichael

Copy link
Copy Markdown
Member

PS I remember that a date headings row is added in the csv and probably fods outputs, but not in the txt or html.

@simonmichael

Copy link
Copy Markdown
Member

We'll want to update package.yaml files too. And I guess there might be something to update in the manual.

@thielema

thielema commented Aug 21, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

I gave some ideas in the initial pull-request.

So you did. I think I'd pick barewide but bare-wide is fine if you prefer it.
(We could also add baretall/bare-tall, with bare as a synonym, for a more mnemonic symmetry - or it might be unnecessary churn.)

@thielema

thielema commented Aug 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Aug 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Aug 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

Thank you.

A quick solution could be to write both period and currency in each column
heading, like so:

That would be great. Or if you prefer

2024 (EUR)  2024 (USD)  2025 (EUR)  2025 (USD)  ...

Let's call it barewide. Add the baretall spelling if you like, if not I might or might not later.

@thielema

thielema commented Aug 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

simonmichael commented Aug 24, 2026 via email

Copy link
Copy Markdown
Member

@thielema

thielema commented Aug 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema
thielema force-pushed the layout-bare-wide branch 2 times, most recently from 889ca21 to 0511890 Compare August 26, 2026 13:59
@simonmichael

Copy link
Copy Markdown
Member

FWIW I don't know if anyone uses --layout=tall (or tidy). Speaking for myself it's always either wide (the default) or bare.

@simonmichael simonmichael removed the needs-discussion To unblock: needs more discussion/review/exploration label Aug 26, 2026
@thielema

thielema commented Aug 27, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Aug 27, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

Hi @thielema. A 1.99.4 release is near, perhaps today. What's the status of this one ?

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

simonmichael commented Sep 10, 2026

Copy link
Copy Markdown
Member

Thanks for working on it @thielema. I do plan to release 1.99.4 (another preview release) later today, but I don't want to create undue pressure for you. I hoped to include this but it seems to be a hard feature to integrate fully. I see there are still some problems, and of course it's still marked Draft.

I currently have OSS credits for AI use, and I did an assisted review; I can share what was found, if you don't object.

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

simonmichael commented Sep 10, 2026

Copy link
Copy Markdown
Member

Ah.. hledger.m4.md and hledger/Hledger/Cli/Commands/*.md are manual source files; .1 and .txt are generated (and you don't need to bother with them, I regenerate them before release). (More details if you're interested, hopefully not too out of date: https://hledger.org/DOCS.html, https://hledger.org/RELEASING.html#phase-1-prepare-main)

Here's the AI review:

Opus (+ edits)> The feature is principled; barewide is the missing cell in the matrix of integrated vs separated symbols x tall vs wide, and it's the one layout that makes balance output directly usable for spreadsheet formulas: one column per (period x commodity), with every commodity present in every row so VLOOKUP/ranges line up.

The diff's shape is reasonable. But the implementation isn't mergeable yet - it produces wrong numbers in compound text reports.

Is the cost reasonable?

+245/-84 sounds heavy for one enum value, but only ~120 lines are the feature. The rest is refactoring, and most of it is stuff we'd want anyway:

  • setDisplayCommodityBare dedupes the same 3-field AmountFormat update that was copy-pasted in 4 places.
  • balanceReportAsSpreadsheetParts extracted from balanceReportAsSpreadsheet, mirroring the existing multiBalanceReportAsSpreadsheetParts - consistent with the structure already there.
  • NonEmpty rows + addRowSpanHeaderNE replace what would otherwise be partial head/tail when splitting the account column off a row.
  • Functor-generalising addHeaderBorders/rawTableContent/addTotalBorders is mechanical and cheap.

The one design cost to push back on: a new positional [CommoditySymbol] argument threaded through five functions (multiBalanceReportAsSpreadsheetParts, multiBalanceRowAsCellBuilders, multiBalanceRowAsText, multiBalanceRowAsCsvText, plus compound). It's meaningless for 4 of the 5 layouts, and it carries an unenforced invariant: it must be a superset of every commodity rendered, because orderedAmounts silently drops amounts whose commodity isn't in displayCommodityOrder. Violating it doesn't crash, it prints zeros. Which is what has happened below.

Cheaper alternative: AmountFormat is already threaded to all these functions and already has displayCommodityOrder. Putting the report-wide list there (via setDisplayCommodityBare fmt allCommodities at the call site) would let all five signatures stay unchanged.

Defects found

1. Compound text reports show wrong numbers. compoundBalanceReportAsText was updated only for the Net: row; each subreport still goes through multiBalanceReportAsTable, which computes commodities from its own rows. Subreports then have different column counts and get concatenated under one header:

$ hledger -f t.journal is -Y --layout=bare-wide        # expenses are 20 EUR and 30 USD
|               || 2024 (USD) | 2025 (USD) |
| expenses:food ||          0 |          0 |     <- should be 20 EUR
| expenses:rent ||          0 |          0 |     <- should be 30 USD
| Net:          ||          0 |          0 |

bs -Y --layout=bare-wide similarly files a USD liability under the 2024 (EUR) heading. The CSV path gets the same data right, so this is purely the text path.

2. Compound CSV header row is ragged. dataHeaderCell sets cellSpan = SpanHorizontal n without the Covered filler cells that horizontalSpan adds. Fine for FODS/HTML, broken for CSV:

"Account","2024","2025"                    <- 3 fields
"Revenues","EUR","USD","EUR","USD"         <- 5 fields

(The non-compound multiBalanceReportAsSpreadsheetParts uses the two-header-row approach with fillers and comes out correct. Two different solutions to the same problem, only one right.)

3. Single-period text columns are misaligned. bareWideLayoutBalanceReportAsText renders each row's amounts pre-padded to that row's own width, then textCell TopLeft left-aligns them:

| assets:bank      ||  0    30    50   |
| equity           ||    0  -100     0 |

Probably wants TopRight - or, as you suggested in the thread, just route single-period bare/barewide through the multi-period renderer and drop this function entirely.

4. Minor HTML regression in all layouts: compound date headers now emit <th colspan="1">. Only set the span when > 1.

5. Dead code to strip: two commented-out imports (Text.Printf, quoteIfSpaced), a commented singleColumnTableInterRowBorder, and two commented liftA2 alternatives. Also dateHeaders >> allCommodities as a repetition idiom is needlessly cryptic.

What works

Multi-period text (the 2024 (AAA) headings), -T/-A columns, non-compound CSV/FODS spanning headers, and budget CSV/FODS all look right. Budget text silently falls back to a bare-like rendering rather than erroring - worth deciding which we want.

No regressions elsewhere: I diffed old vs new across 72 command x layout x format combinations (only the colspan="1" noise differed), and the functional suite passes 1784/1784 on the PR build.

Suggestions

  • fix the compound-text path (thread the union into multiBalanceReportAsTable, or error out for compound text barewide)
  • add the CSV header filler cells
  • fix single-period text alignment
  • strip the dead/commented code
  • the barewide rename
  • docs: Balance.md's layout section and support matrix, plus the --layout flag help in Balance.hs
  • functional tests covering bal/bs/is x txt/csv - the two data bugs above would both have been caught by a single is -Y --layout=barewide test

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

PS here's a repro for that defect 1:

2025-01-01
    income:salary   -50 USD
    assets:bank

2025-03-01
    expenses:food    20 EUR
    assets:bank
$ hledger -f min.journal is --layout=bare-wide
|               || 2025-01-01..2025-03-01 (USD) |
| Revenues      ||                              |
| income:salary ||                           50 |
| Expenses      ||                              |
| expenses:food ||                           20 |   <- this is 20 EUR, under a (USD) heading
| Net:          ||                          -20 |   <- should be -20 EUR and 50 USD

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

I'm sorry for pasting AI responses, let me know if I should stop. But I find them a useful complement and food for thought and future planning. Here's one more.

Opus> Agreed on the diagnosis and the fix, with one clarification:

  • "pass allCommodities to multiBalanceReportAsTable" - correct, and that's exactly the first option in the suggestions above ("thread the union into multiBalanceReportAsTable"). It also fixes it structurally: passing the union makes every subreport table have the same column count, which is what concatTables needs.
  • "but your AI suggested to avoid that" - not quite. The pushback was on adding another positional [CommoditySymbol] parameter carrying an invariant the types don't enforce, not on threading the value at all. The alternative offered (AmountFormat.displayCommodityOrder, already threaded and already meaning exactly this) still passes the same data. That said, the objection has some force for the text path specifically: multiBalanceReportAsTable builds its own AmountFormat internally rather than receiving one, so that route would mean adding an AmountFormat parameter instead, which is no saving. So: fix the bug the direct way, and treat the AmountFormat consolidation as optional cleanup for the spreadsheet path only, or skip it.
  • "the deeper problem is that Text output takes a different code path" - agreed, and that's the source of this PR's cost. It's the root cause of both defect 1 and defect 3, and it's why one new enum value needs branches in two parallel rendering stacks. Your earlier idea in this thread, routing bare/barewide text through the spreadsheet/multi-period renderer, is the principled fix, but it's a bigger refactor than this PR and fine to defer, as long as we state it as deferred rather than leave it implicit.

We don't have to achieve perfection today - if you want to get what's working into 1.99.4, it would be ok to just disallow --layout barewide with text output for now.

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

Is this a good idea or is this abuse?

AmountFormat covers a lot of things to do with displaying amounts, and this seems to be another of those. So it sounds ok to me on the surface. But I'm not as deep into this as you are, I defer to your judgement.

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@thielema

thielema commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor Author

@simonmichael

Copy link
Copy Markdown
Member

Fantastic, thank you @thielema !

@simonmichael
simonmichael marked this pull request as ready for review September 10, 2026 23:58
@simonmichael
simonmichael merged commit db64ec7 into hledgerorg:main Sep 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants