Skip to content

ui-hints.yaml

ui-hints.yaml is an optional file for presentation overrides that have no natural home in the database. Your DDL and your COMMENT text already give Kozou enough to render a usable Admin UI, expose a REST API, and emit MCP context — ui-hints.yaml exists only for the handful of cosmetic decisions that are not properties of the schema itself, such as forcing a particular input widget on a column or giving a table a friendlier label.

Because the database is the single source of truth (see Postgres as source of truth), this file is deliberately small. The system works without it. Reach for it only when a default is wrong and there is no good way to fix it in COMMENT.

Kozou runs with no ui-hints.yaml at all. When the file is absent — or present but empty — every label, widget, and display field comes from the schema plus sensible defaults. create-kozou scaffolds a near-empty ui-hints.yaml (an tables: {} placeholder with commented examples) precisely so you can ignore it until you have a reason not to.

The path is configured in kozou.config.yaml under uiHints.path, and missing files are tolerated:

uiHints:
path: ./ui-hints.yaml # the file need not exist

See kozou.config.yaml for the rest of the configuration surface.

For each property Kozou resolves a value from up to three sources, in order. The first source that supplies a value wins:

PropertyResolution order
Column widgetui-hints.yaml@widget COMMENT tag → heuristic default
Column labelui-hints.yaml → first line of the column COMMENT → column name
Table labelui-hints.yaml → first line of the table COMMENT → table name
Display fieldui-hints.yaml → heuristic default

A ui-hints.yaml entry therefore overrides both the @widget COMMENT tag and the heuristic Kozou would otherwise infer. It sits at the top of the chain: if you set a widget here, the @widget tag in the column’s COMMENT is ignored for that column.

The heuristics it overrides are the same defaults documented for emitted surfaces: foreign keys default to a relation-select, columns with a detectable enum to an enum-select, uuid columns to uuid, numeric types to number, and so on. The display field falls back to the first column that exists among name, title, label, display_name, name_ja, name_en, then the primary key.

The file has two top-level keys, tables and views, each a map keyed by object name. Every field below is optional — include only what you want to change.

  • Per-table label — the display name shown for a table or view.
  • Per-table displayField — the column used to represent a row when it is referenced elsewhere (for example, the text shown for a foreign-key relation instead of the raw id).
  • Per-column label — the display name shown for a column.
  • Per-column widget — the input/display widget for a column. One of: text, textarea, number, boolean, date, datetime, enum-select, relation-select, json, image-url, uuid, currency.
  • Per-column readonly — mark a column as read-only in the Admin UI.
  • Per-column relation — for a relation-select column, a labelField and searchFields[] controlling how the referenced rows are labelled and searched.

Column ordering follows the order in which you list columns under a table or view: entries are written as a map, and the keys are presented in source order. Columns you do not mention keep their schema position.

There is no per-column description or @ai equivalent here — that text belongs in the database COMMENT, where every emitted surface can read it.

tables:
<table_name>:
label: <string>
displayField: <column_name>
columns:
<column_name>:
label: <string>
widget: <widget>
readonly: <bool>
relation:
labelField: <column_name>
searchFields: [<column_name>, ...]
views:
<view_name>:
label: <string>
columns:
<column_name>:
label: <string>
widget: <widget>

Suppose products has a status column constrained to draft / published / archived. Kozou already detects the enum and renders an enum-select, but you want a clearer label, and you want the long description column to use a multi-line editor:

tables:
products:
label: Catalog
displayField: name
columns:
status:
label: Publication status
widget: enum-select
description:
widget: textarea
sku:
readonly: true

Here the status label and widget, the description widget, and the sku read-only flag override whatever the COMMENT tags or heuristics would have produced — everything else on products is left to the defaults.

A relation override looks like this, for an orders table whose author_id points at an authors table:

tables:
orders:
columns:
author_id:
label: Author
widget: relation-select
relation:
labelField: full_name
searchFields: [full_name, email]

Every entry in ui-hints.yaml is a fact about your data expressed outside your database — which means it can drift out of sync with the schema, and it is one more file to keep in agreement with the COMMENT text. Treat that as a cost.

  • Prefer fixing presentation at the source. A wrong widget is often better corrected with a @widget tag in the column COMMENT, and a wrong label with a clearer first line in the COMMENT — both travel with the schema and are visible to AI agents reading MCP context.
  • Use ui-hints.yaml for the residue: choices that are genuinely about presentation and have no home in SQL, or one-off overrides where editing the database COMMENT is not worth it.
  • Validation runs on load. If you reference a table, view, or column that does not exist, Kozou warns; an unparseable or schema-invalid file is reported as an error. Keeping the file small keeps those references easy to verify against the schema.

The goal is a ui-hints.yaml that is mostly empty, holding only the overrides the database cannot express.