Skip to content

docs(ogar): Redmine Query/QueryColumn harvest — concrete T2 spec#82

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/ogar-redmine-query-harvest
Jun 19, 2026
Merged

docs(ogar): Redmine Query/QueryColumn harvest — concrete T2 spec#82
AdaWorldAPI merged 1 commit into
mainfrom
claude/ogar-redmine-query-harvest

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

Harvests Redmine's Query + QueryColumn + helpers pattern as the concrete spec for T2 (HtmlListView). Sibling to the Northstar plan (CLASSVIEW-MATERIALIZATION-PLAN.md); doc-only, 481 LOC.

Apple meets Apple — they spent 17 years iterating to this shape; we adopt the design and replace the substrate.

The headline finding

Redmine's app/views/issues/_list.html.erb (61 LOC) is substrate-agnostic. It iterates query.inline_columns, calls column_header(query, col) + column_content(col, item), and renders every list page in the app. They didn't write it per controller — they wrote it once.

That's the same insight WoA-rs codified with RouteSpec/HandlerKind and our Northstar plan codifies with ClassView/ArtifactKind. The design work for the HTML render side is done; we map their shape onto our substrate.

What the harvest captures

§1 — What Redmine evolved

  • QueryColumn with 8 render-meta properties (sortable, groupable, totalable, inline, frozen, default_order, caption, name) — the exact per-column metadata a generic list view needs.
  • 4 specialized subclasses (TimestampQueryColumn, WatcherQueryColumn, QueryAssociationColumn, QueryCustomFieldColumn) covering the irregular shapes.
  • 6 per-resource Query subclasses each declaring ~25 columns = ~150 column declarations.
  • 3 render helpers: column_header / column_content / column_value (the last is a 12-arm case — the cell formatter library, 17 years' worth).

§2 — Map onto ClassView (point-by-point)

Redmine Our substrate
query.inline_columns / block_columns FieldMask & inline_set / block_set
QueryColumn.name / .caption FieldRef.predicate_iri / .label
8 render-meta properties RenderColumn sidecar in binding-struct (not on contract — anti-pattern #2)
column_value 12-arm case ColumnKind enum + per-kind askama sub-templates
6 Query subclasses 0 (ClassView is the spine)

§3 — Concrete T2 spec

HtmlListViewCtx binding-struct + RenderColumn + ColumnKind enum + askama template skeleton + ~10 cell sub-templates (one per kind: IdLink, PrimaryLink, RecordRef, RichText, ProgressBar, RelationList, Hours, AttachmentList, UserList, Plain).

Cells are pre-rendered in Rust at row-build time — the spine template emits {{ cell.body_html|safe }}, no runtime polymorphism in askama.

§4 — TODO surface for T2's PR

Five concrete checkboxes for the next implementation PR.

§6 — The Apple-meets-Apple summary

Redmine evolved We inherit
One spine partial per resource (26 of them) One spine template, period
6 Query subclasses × ~25 columns 0 subclasses; ClassView + codebook
12-arm case formatter ~10 cell sub-templates, append-only
50 view helpers askama filters + binding-struct accessors
Untyped jsonb everywhere Typed canonical layer; SurrealQL DDL intercepts
17 years of cell-rendering wisdom Lifted as T2's askama kit spec

Why land this as a doc PR first

T2's PR will be substantial (binding-struct + ~10 templates + emitter + tests). Landing the spec first lets the implementation hew to a written contract, and gives codex / future sessions the harvested rationale instead of having to re-derive from app/views/issues/_list.html.erb.

Scope

Doc-only. No production code, no test changes. docs/integration/REDMINE-QUERY-HARVEST.md is additive.

"Apple meets Apple." Redmine spent 17 years iterating to a clean
dynamic-column render pattern (Query + QueryColumn + helpers); their
_list.html.erb is substrate-agnostic and dispatches every list page in
the app through one body. Same insight WoA-rs codified and the Northstar
plan calcifies. We harvest, don't reinvent.

Adds docs/integration/REDMINE-QUERY-HARVEST.md (sibling to the Northstar
plan). Captures:

§1 The Redmine pattern they evolved:
- QueryColumn with 8 render-meta properties (name, caption, sortable,
  groupable, totalable, default_order, inline, frozen).
- Specialised subclasses: TimestampQueryColumn, WatcherQueryColumn,
  QueryAssociationColumn, QueryCustomFieldColumn (+ chained variant).
- Per-resource Query subclass declares ~25-column `available_columns`
  list. 6 subclasses today (IssueQuery / ProjectQuery / …).
- Three render helpers: column_header / column_content / column_value.
  column_value is a 12-arm case statement — the formatter library.
- One spine partial (_list.html.erb, 61 LOC) renders every list page.

§2 Map onto ClassView (point-by-point):
- query.inline_columns / block_columns → FieldMask & inline_set / block_set.
- QueryColumn.name → FieldRef.predicate_iri.
- QueryColumn.caption → FieldRef.label.
- Render-meta (sortable / groupable / …) → RenderColumn sidecar in the
  binding-struct, NOT extending FieldRef (per Northstar anti-pattern #2).
- Cell formatters → per-kind askama sub-templates dispatched via
  ColumnKind enum (append-only).
- 6 Query subclasses → 0 (ClassView is the spine).

§3 Concrete T2 spec:
- HtmlListViewCtx binding-struct shape + RenderColumn + ColumnKind + …
- Template skeleton (mirror of _list.html.erb body, FieldMask-driven).
- ~10 cell sub-templates one-per-ColumnKind (id_link, primary_link,
  record_ref, rich_text, progress_bar, relation_list, hours,
  attachment_list, user_list, plain). Append-only.
- Pre-render cells in Rust at row-build time; spine template emits
  `{{ cell.body_html|safe }}` — no runtime polymorphism in askama.

§4 TODO surface for T2's PR.
§5 Where each piece lives across crates.
§6 The Apple-meets-Apple summary — 26 partials × 6 Query subclasses →
1 spine template × 0 subclasses, with the typed canonical layer
replacing untyped jsonb + Ruby helpers + per-controller views.

doc-only; no production code touched.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52ad220adf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

> column-dispatch render pattern (`Query` + `QueryColumn` + helper trio).
> This doc harvests it as a *spec*, maps each piece onto our
> `ClassView` / `ogar-vocab::Class` / `ogar-render-askama` substrate, and
> derives the concrete shape of T2 (`HtmlListView`) so the askama template

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep T2 aligned with the Northstar plan

This document repeatedly defines T2 as HtmlListView, but the plan it tells readers to use as the source of truth lists T2 as TsInterface in docs/integration/CLASSVIEW-MATERIALIZATION-PLAN.md §3, with T3–T5 already assigned to the remaining artifact emitters. In the next implementation cycle, following this new TODO would cause the implementer to build the wrong PR and skip the planned TypeScript emitter, so either rename this work item or update the Northstar plan in the same change.

Useful? React with 👍 / 👎.

Comment on lines +339 to +342
/// The pre-formatted body of the cell. For `Plain` kind this is the
/// late-resolved string from RenderRow; for richer kinds, the sub-
/// template's already-rendered output.
body_html: askama::filters::SafeString,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not mark plain cell strings as safe HTML

When a generic list renders Plain cells containing user-controlled string/text values, this spec stores the late-resolved string in SafeString and the template later emits it with |safe, which bypasses Askama's HTML escaping for those cells. The rich sub-template outputs can be marked safe after they escape their own inputs, but plain values should stay as escaped text or carry an explicit escaped-vs-HTML distinction before the implementation copies this binding shape.

Useful? React with 👍 / 👎.

@AdaWorldAPI AdaWorldAPI merged commit b96e4f8 into main Jun 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants