Story: Consolidate history dialogs onto HistoryDialogBase
Table of Contents
This page documents a story in Sprint 19. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
Goal
Every entity history dialog derives from HistoryDialogBase and shares
one implementation of the common machinery, so that fixes and UX
changes land once instead of 67 times, and the per-dialog code shrinks
to what is genuinely entity-specific (field comparisons and labels).
Survey (2026-06-05, the day the CI broke on exactly this duplication):
- 67
*HistoryDialogclasses across ores.qt sub-libraries. HistoryDialogBasealready exists in ores.qt.api (signals,markAsStale(),code()) but has exactly one adopter (WorkspaceHistoryDialog).- 64 dialogs duplicate the version-list/load pipeline
(
onVersionSelected,loadHistory,QFutureWatcherplumbing). - 6 dialogs (Account, SystemSetting, ChangeReason,
ChangeReasonCategory, Country, Currency) hand-roll a changes tab
with field-level diffs using four different idioms: a free
function, per-call lambdas, a
CHECK_DIFF_STRINGmacro, and inline if-blocks. The Account variant named a class-private alias from an anonymous-namespace function, breaking GCC/AppleClang builds (fixed tactically; this story is the proper fix).
Design direction — two layers:
- Server-side diffs (the deeper fix). Computing field-level diffs
in the UI is the wrong place: when history views are added to the
shell, Wt and HTTP frontends the same comparison code would be
repeated in each. Instead:
- A per-entity mapper converts a domain type to an ordered list of (field name, value) strings — one definition per entity, server side, reusable by every frontend and candidate for codegen from the entity models.
- A generic NATS history message returns the computed diffs between consecutive versions (field, old value, new value), rather than shipping two full versions for the client to compare. Frontends only render.
- Qt consolidation onto the base.
DiffResult(list of (field, (old, new)) rows) is defined once inHistoryDialogBase.- The base owns the changes-tab rendering flow and calls an
abstract
calculateDiff(current, previous)template method; derived dialogs implement only the per-entity field comparisons. Once the server-side diff message lands,calculateDiffimplementations collapse into rendering the server-provided rows, and the template method may disappear entirely. - Shared
checkString/checkInt/checkBoolhelpers live with the base so all dialogs format values identically in the interim. - Version-list population, async load, stale handling and toolbar wiring migrate into the base (or an intermediate templated CRTP helper if the version types resist a common interface).
Architecture
Three layers, from the wire up; full implementation detail lives in each task's Plan.
- ores.diff — a dependency-light leaf component owning the
vocabulary of "what changed":
field_value,diff_entry,diff_result, and acomputeengine. Every domain service computes with it; every frontend renders from it; std + rfl only. - Per-entity field mappers, server side — domain type → ordered, human-labelled, rendered field list, living beside the entity's other mappers. Hand-written for the pilot; a codegen candidate later.
- Extended history responses — the existing typed per-entity
subjects remain; each version gains
fields(full render — the detail panel needs complete values) andchanges(diff vs the previous version). Frontends — Qt today; shell, Wt, HTTP tomorrow — are dumb renderers. Full domain payloads stay during migration.
Migration runs A→D: grow the base and migrate the six hand-rolled dialogs (codegen-free); pilot currency end to end (codegen-free); template rollout (gated on the codegen org-model migration — a template-only edit, but the regeneration sweep must not race the in-flight model migration); codegen the mappers (deferred).
Status
| Field | Value |
|---|---|
| State | STARTED |
| Parent sprint | Sprint 19 |
| Now | Shell unified diff; phase C gated on codegen migration. |
| Waiting on | Nothing. |
| Next | Phase C: template rollout (gated on codegen migration). |
| Last touched | 2026-06-05 |
Acceptance
- Every entity history dialog derives from
HistoryDialogBase; the per-dialog code is limited to what is genuinely entity-specific. - Field-level diffs are computed server-side; no frontend holds comparison logic.
- ores.diff exists with exhaustive engine tests; mappers and handlers are tested at their own layers.
- The shell can render history as a unified diff.
- No codegen change is forced while the org-model migration is in flight; the template rollout is explicitly gated.
Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Design the history diff architecture and testing strategy | DONE | 2026-06-05 | 2026-06-05 | Design distributed into this story's Architecture and the tasks' Plans. |
| Grow HistoryDialogBase and migrate the six hand-rolled dialogs | DONE | 2026-06-05 | 2026-06-05 | Scope grew to all 64 version-history dialogs; event-log dialogs renamed to Audit. PR 1080. |
| Create the ores.diff component | DONE | 2026-06-05 | 2026-06-05 | Flat diff model + engine, std+rfl only; 14 tests. PR 1091. |
| Pilot server-side history diffs end to end on currency | DONE | 2026-06-05 | 2026-06-05 | Currency mapper + extended response + handler compose; dialog renders server rows; dogfooded. PR 1097. |
| Adopt the base in the history-dialog template and roll out | BACKLOG | Phase C: template target form + per-domain rollout. Gated on codegen org-model migration. | ||
| Codegen field mappers from entity models | BACKLOG | Phase D, deferred: generate mappers once the hand-written shape is proven. | ||
| Show history as a unified diff in ores.shell | DONE | 2026-06-05 | 2026-06-06 | Unified-diff rendering of the changes rows in the shell; no comparison logic client-side. |
Decisions
- A dedicated
ores.diffcomponent (not a corner of ores.utility): the types cross every domain service and every frontend; a leaf component keeps the dependency arrows clean and matches the many-small-components style. - Keep the typed per-entity history subjects; extend responses rather than introducing a generic untyped subject.
- Responses carry both
fieldsandchanges— mildly redundant, but it makes every frontend a dumb renderer, which is the point. - Phases A and B are codegen-free by construction; the template rollout is gated on the codegen org-model migration to avoid a circular dependency; codegen'd mappers are deferred outright.