Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/scripts/sync-from-marfa.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,28 @@ check "a subtype that differs says its hints and merge policy as values" \
check "the parent says its own hints once, and a subtype equal to it adds none" 1 \
"$(grep -cE '^\| \[`title_field`\].*\| `title` +\|$' "${drift_file}")"

# Keys a type and its fields gain: a plural, field labels, a list of entries with members, a range,
# a status field's words and groups, and an edge type's acyclic flag.
echo "a type catalog with plurals, labels, members, ranges and statuses"
fixture labels "${work}/untagged.json"
catalog_with 'n = c["types"][0]; n["plural"] = "Notes"; n["fields"]["title"]["label"] = "Heading"; n["fields"]["rating"] = {"type": "integer", "description": "A rating", "minimum": 1, "maximum": 5}; n["fields"]["progress"] = {"type": "status", "description": "Progress", "status_values": [{"value": "planned", "group": "not_started"}, {"value": "done", "group": "completed"}]}; n["fields"]["links"] = {"type": "array", "description": "Links", "items_type": "object", "items_fields": {"value": {"type": "url", "label": "Link", "description": "The link", "required": True}}}; c["edge_types"][1]["acyclic"] = True' \
> "${work}/labels/marfa/core/marfa-core/src/builtin_catalog.json"
sync labels
labels_file="${work}/labels/docs/src/generated/catalog/core.note.md"
check "plurals, labels, members, ranges and statuses: the script succeeds" 0 "${status}"
check "the plural is a setting" 1 "$(grep -cF '| `plural`' "${labels_file}")"
check "a field's label has a column that appears with it" 1 "$(grep -cE '^\| `title` +\| Heading +\|' "${labels_file}")"
check "a range is in the kind" 1 "$(grep -cF '`integer` (1 to 5)' "${labels_file}")"
check "a status field's words are listed with their groups" 1 "$(grep -cF 'Values: `planned` in `not_started`, `done` in `completed`.' "${labels_file}")"
check "a member of a list of entries follows its field, with its label" 1 "$(grep -cE '^\| `links\[\]\.value` +\| Link +\| `url` +\| Yes' "${labels_file}")"
check "an acyclic edge type says so" 1 "$(grep -cF '**acyclic:** `true`' "${work}/labels/docs/src/generated/catalog/edges.md")"
refuse_catalog "a field with an empty label" "${cat_file} has a field core.note.body whose label is not a label. Nothing has changed." "$(catalog_with 'c["types"][0]["fields"]["body"]["label"] = ""')"
refuse_catalog "a status with no words" "${cat_file} has a status core.note.body whose status_values are not a list of values and groups. Nothing has changed." "$(catalog_with 'c["types"][0]["fields"]["body"]["type"] = "status"; c["types"][0]["fields"]["body"]["status_values"] = []')"
refuse_catalog "status words on a field that is not a status" "${cat_file} has status_values on core.note.body, which is not a status. Nothing has changed." "$(catalog_with 'c["types"][0]["fields"]["body"]["status_values"] = []')"
refuse_catalog "a range that is not a number" "${cat_file} has a minimum on core.note.body that is not a number. Nothing has changed." "$(catalog_with 'c["types"][0]["fields"]["body"]["minimum"] = "1"')"
refuse_catalog "members on a field that is not a list of objects" "${cat_file} has items_fields on core.note.body, which is not an array of objects. Nothing has changed." "$(catalog_with 'c["types"][0]["fields"]["body"]["items_fields"] = {"a": {"type": "string", "description": "A"}}')"
refuse_catalog "an acyclic flag that is not true or false" "${cat_file} has the edge type in-collection with an acyclic that is not true or false. Nothing has changed." "$(catalog_with 'c["edge_types"][0]["acyclic"] = "yes"')"

# spec_with <python statements on the spec s>: the fixture's spec, changed.
spec_with() {
python3 -c 'import json, sys; s = json.load(open(sys.argv[1])); exec(sys.argv[2]); print(json.dumps(s))' "${work}/untagged.json" "$1"
Expand Down
2 changes: 1 addition & 1 deletion content/docs/catalog/(types)/(core)/entity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ identifier: core.entity
"name": "Priya Raman",
"given_name": "Priya",
"family_name": "Raman",
"email": "priya.raman@example.com",
"emails": [{ "value": "priya.raman@example.com", "label": "work" }],
"organization": "Harbour Press",
"job_title": "Managing editor",
"birthday": "1988-04-17"
Expand Down
2 changes: 1 addition & 1 deletion content/docs/catalog/(types)/(core)/task.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ identifier: core.task
"title": "Renew passport",
"body": "The form is online. It needs a countersigned photo.",
"due_at": "2026-12-01",
"status": "pending",
"status": "not_started",
"priority": "high"
}
}
Expand Down
1 change: 1 addition & 0 deletions content/docs/catalog/(types)/field-kinds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Each field of a type holds one kind of value, named by its `type`. **A value Mar
| `datetime` | A date and time with a zone, such as `2026-03-01T09:30:00Z`, or a date alone, as `YYYY-MM-DD`. A time with no zone is refused. |
| `date` | A date alone, as `YYYY-MM-DD`. A date with a time is refused. |
| `enum` | One of the field's `enum_values`. |
| `status` | One of the words in the field's `status_values`. Each word belongs to one of four groups: `not_started`, `in_progress`, `completed` or `canceled`. |
| `array` | A list. `items_type` names the kind of its elements, and `maxItems` caps their number. |
| `object` | A JSON object. |
| `thumbnail` | A PNG, JPEG or WebP data URI that decodes to at most 16 KiB. |
Expand Down
6 changes: 3 additions & 3 deletions content/docs/catalog/(types)/your-own-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ description: "The rules a type you register must meet: its identifier, fields, p

| Rule | Marfa allows or refuses |
| --- | --- |
| Keys | A field definition takes `type`, `description`, `required`, `enum_values`, `items_type`, `format`, `searchable`, `maxLength` and `maxItems`, and nothing else, such as `minimum`. |
| Keys | A field definition takes `type`, `label`, `description`, `required`, `enum_values`, `status_values`, `items_type`, `items_fields`, `format`, `searchable`, `maxLength`, `maxItems`, `minimum` and `maximum`, and nothing else. |
| `type` | One of the [field kinds](/catalog/field-kinds). |
| `enum` | Needs `enum_values`, a list of at least one string. |
| `array` | Needs `items_type`. An `items_type` that isn't a field kind leaves the elements unchecked. |
Expand Down Expand Up @@ -64,9 +64,9 @@ A replacement or deletion that breaks another type's claim is refused the same w
| --- | --- | --- |
| `title_field` | The name of the field that gives an item its name. | A field the type and its ancestors don't declare, or a `thumbnail` field. |
| `body_field` | The name of the field that holds the item's text. **The value is Markdown.** Apps render it, and a device reads it to make [edges](/catalog/edges) from the links it contains. Marfa stores the value as sent and doesn't check it. | The same as `title_field`. |
| `icon` | The name of a [Lucide](https://lucide.dev/icons/) icon in kebab-case, such as `star` or `file-music`. | A name outside Lucide 1.61.0, the one release Marfa pins: a different case, a name from a later release, or an icon Lucide removed. **Use the canonical name, not an alias:** `home` is refused and `house` is accepted. Refused `400 invalid_schema`, naming `display_hints.icon`. |
| `icon` | An [Iconify](https://iconify.design/) icon ID, `<set>:<name>`, from the `lucide` set (`@iconify-json/lucide` 1.2.149) or, for brand marks, the `simple-icons` set (`@iconify-json/simple-icons` 1.2.101), such as `lucide:star` or `simple-icons:github`. | A bare name such as `star`, another set, a name the set's version lacks, an alias such as `lucide:alert-circle`, an icon the set marks hidden, or a wrong-case or padded ID. Refused `400 invalid_schema`, naming `display_hints.icon`. |

Every operation that takes a type definition applies the same rule, including [normalize a type definition](/api-reference/types/normalizeType). The names Marfa accepts are in the [generated list](https://github.com/withmarfa/marfa/blob/main/packages/types/generated/lucide-icons.json).
Every operation that takes a type definition applies the same rule, including [normalize a type definition](/api-reference/types/normalizeType). The IDs Marfa accepts are in the generated lists [`icon-set-lucide.json`](https://github.com/withmarfa/marfa/blob/main/packages/types/generated/icon-set-lucide.json) and [`icon-set-simple-icons.json`](https://github.com/withmarfa/marfa/blob/main/packages/types/generated/icon-set-simple-icons.json).

**Each hint comes from the nearest type that sets it:** the type itself, then its parent, then that type's parent. A type that sets only an `icon` still shows the title and body its parent names. `GET /types/{id}` answers each hint after that choice, and `GET /types` lists the hints each type declares itself, with the owner's icon in place of the author's.

Expand Down
Loading
Loading