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
2 changes: 1 addition & 1 deletion content/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file sits above `content/docs/` on purpose: the docs collection publishes e

## Placing a page

- Each folder directly under `content/docs/` is a tab. `(overview)/` and `(reference)/` are folder groups, so their pages sit at the site root (`/files`, `/cli`). `build/`, `run/` and `catalog/` keep their names in the URL, and so does `(reference)/api-reference/`. The Catalog sidebar has three labeled sections: Data (Items, Types, Edges), Events and access, and Integrations (Connectors). The Catalog tab's Types pages sit in a folder group, `catalog/(types)/`, which the sidebar shows as a closed folder named Types. Its own page is `types.mdx` (`pagesIndex` in its `meta.json`, so the folder opens on `/catalog/types`), and it holds Type rules and Field kinds and two more folder groups, `(core)/` and `(system)/`, shown as closed folders, "Core types" and "System types". No group adds to an address: a type page stays at `/catalog/note`.
- Each folder directly under `content/docs/` is a tab. `(overview)/` and `(reference)/` are folder groups, so their pages sit at the site root (`/files`, `/cli`). `build/`, `run/` and `catalog/` keep their names in the URL, and so does `(reference)/api-reference/`. The Catalog sidebar has three labeled sections: Data (Items, Types, Edges), Events and access, and Integrations (Connectors). The Catalog tab's Types pages sit in a folder group, `catalog/(types)/`, which the sidebar shows as a closed folder named Types. Its own page is `types.mdx` (`pagesIndex` in its `meta.json`, so the folder opens on `/catalog/types`), and it holds Type rules, Display hints and Field kinds and two more folder groups, `(core)/` and `(system)/`, shown as closed folders, "Core types" and "System types". No group adds to an address: a type page stays at `/catalog/note`.
- A new page needs its file and an entry in its folder's `meta.json`. `content/docs/meta.json` orders the tabs. Build, Run and the Catalog each open on an `index.mdx` that their `meta.json` lists first; a page that belongs in Build (code against an instance) or Run (an instance and its care, with no code) goes in that folder, under the group a reader would name.
- A slug in `(overview)/` or `(reference)/` must not repeat a slug in either group. It must not be a name the Worker answers at the root (`api`, `assets`, `mcp`, `llms`, `llms-full`, `docs-map`, `version`) or a tab folder (`build`, `run`, `catalog`), or `docs`, which `marfa docs` strips as a prefix.
- The Catalog tab lists names and shapes and explains nothing. Each top-level type, core or system, has a page (`src/lib/catalog.test.ts` holds them to `src/generated/catalog/builtin-catalog.json`) that takes its title and description from the type and includes the type's generated file. A core type's page ends with one `## Example` item that the test checks; Marfa writes system items itself, so their pages have none. Add a type's page, in the folder of its family and in that folder's `meta.json`, when marfa ships a type, and leave explanation to the Overview, Build and Run. Include `catalog/edge-types.md`, `permissions.md` and `event-types.md` on Catalog pages only; every other page links to the Catalog page.
Expand Down
2 changes: 1 addition & 1 deletion content/docs/(overview)/items-and-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ A type also tells an app how to show its items, through three **display hints**:
- **The text is Markdown.** An app renders it, and a device reads the links in it to make [edges](/edges).
- **The icon is an ID** such as `lucide:calendar`, or `simple-icons:github` for a brand. The owner can choose another for any type, including the ones Marfa ships.

Each hint comes from the nearest type that sets it, so a child that sets only an icon keeps its parent's title and text. [Type rules](/catalog/type-rules#display-hints) has the details.
Each hint comes from the nearest type that sets it, so a child that sets only an icon keeps its parent's title and text. [Display hints](/catalog/display-hints) has the details.

## Where an item came from

Expand Down
2 changes: 1 addition & 1 deletion content/docs/(reference)/swift-sdk/items.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ do {

## Names and previews

`items.displays(of:)` gives each item's `ItemDisplay`: its `name` and a `preview` of its body field with the Markdown removed, up to 240 characters. `displays(for:)` takes `ItemDisplayInput` values for an item not yet saved. **Marfa's core works both out from the copy's catalog**, so every app names an item alike. Where the instance would say `Untitled`, `name` is `nil`, and the app shows its own words.
`items.displays(of:)` gives each item's `ItemDisplay`: its `name` and a `preview` of its body field with the Markdown removed, up to 240 characters. `displays(for:)` takes `ItemDisplayInput` values for an item not yet saved. **Marfa's core works both out from the copy's catalog**, by the rule in [Names and previews](/catalog/display-hints#names-and-previews), so every app names an item alike. Where the instance would say `Untitled`, `name` is `nil`, and the app shows its own words.

```swift
let notes = try await copy.items.list(ListFilters(types: ["core.note"]))
Expand Down
2 changes: 1 addition & 1 deletion content/docs/(reference)/swift-sdk/server-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ A move throws `server` with the code `type_move_blocked` when a setting names th

## Draw a type's icon

A type's icon is an Iconify ID: `lucide:book-open` from the [Lucide](https://lucide.dev/icons/) release Marfa pins, or `simple-icons:github` for a brand mark from [Simple Icons](https://simpleicons.org). **`ItemType.icon` holds the icon the type resolves to**: the owner's choice for the type or its nearest parent, else the nearest author's. It's `nil` when no type in the chain sets one. [Display hints](/catalog/type-rules#display-hints) explains.
A type's icon is an Iconify ID from one of the two icon sets Marfa pins: `lucide:book-open` from [Lucide](https://lucide.dev/icons/), or `simple-icons:github` for a brand mark from [Simple Icons](https://simpleicons.org). **`ItemType.icon` holds the icon the type resolves to**: the owner's choice for the type or its nearest parent, else the nearest author's. It's `nil` when no type in the chain sets one. [Display hints](/catalog/display-hints) explains.

The `MarfaIcons` product draws the ID:

Expand Down
4 changes: 2 additions & 2 deletions content/docs/build/change-a-type.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ let normalized = try await api.check(itemType: definition)

The answer's `type` is your definition with its defaults made explicit, and `registered_type` is the registered one in the same form. Compare those two, not `resolved_type`, which adds what the registered type inherits. Both registered forms are `null` when the identifier isn't registered. An unknown schema key, anywhere in a definition, is refused `400 invalid_schema`.

**A replacement keeps the icon the owner chose** for the type (see [the owner's icon](/catalog/type-rules#the-owners-icon)), and `registered_type` shows the icon you registered, not the owner's.
**A replacement keeps the icon the owner chose** for the type (see [the owner's icon](/catalog/display-hints#the-owners-icon)), and `registered_type` shows the icon you registered, not the owner's.

**Sending back what a `GET` answered changes only what you changed.** A `GET` answers the display hints a type inherits as well as its own. A hint you send with the value the type inherits isn't stored, so the type keeps following its parent and the owner's icon. A hint the type sets itself stays as it is. A change to `title_field` or `body_field` changes how the server and every device [name and preview](/catalog/type-rules#names-and-previews) the type's items.
**Sending back what a `GET` answered changes only what you changed.** A `GET` answers the display hints a type inherits as well as its own. A hint you send with the value the type inherits isn't stored, so the type keeps following its parent and the owner's icon. A hint the type sets itself stays as it is. A change to `title_field` or `body_field` changes how the server and every device [name and preview](/catalog/display-hints#names-and-previews) the type's items.

Then replace the type:

Expand Down
2 changes: 1 addition & 1 deletion content/docs/build/connector-pull-and-push.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ An item's natural key is its `source`, from the connector's key, and its `source

`tier: "feed"` puts the item in the feed, where a person or an app decides whether to keep it. `occurred_at` is when the record happened at the vendor. `atomic: false` writes the records that pass and reports a refused one as `errored`; by default, one refused record **rolls the whole batch back**.

**Write an item's text as Markdown.** Marfa stores the field its type names in [`display_hints.body_field`](/catalog/type-rules#display-hints) unchecked, so convert a vendor's HTML first, or an app shows the tags.
**Write an item's text as Markdown.** Marfa stores the field its type names in [`display_hints.body_field`](/catalog/display-hints#the-hints) unchecked, so convert a vendor's HTML first, or an app shows the tags.

## Write only what changed

Expand Down
4 changes: 2 additions & 2 deletions content/docs/build/define-a-type.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ marfa types scaffold app.example.receipt --parent core.file.image \
--required merchant > receipt.json
```

A field is `NAME:TYPE`, an enum `NAME:enum:one,two` and an array `NAME:array:TYPE`. The command refuses what registration would refuse, and says why. It can't set a field's `description` or `format`, so add them to the file.
A field is `NAME:TYPE`, an enum `NAME:enum:one,two`, an array `NAME:array:TYPE` and a [status field](/catalog/field-kinds#status-words) `NAME:status:WORD=GROUP,WORD=GROUP`, such as `stage:status:open=not_started,closed=completed`. The command refuses what registration would refuse, and says why. It can't set a field's `description` or `format`, so add them to the file.

### Make it readable as another type

Expand Down Expand Up @@ -115,7 +115,7 @@ Marfa made the label from the identifier, and `provenance.created_by` names who

`marfa types get app.example.receipt` (`GET /types/{id}`) answers the type with the properties it inherits, such as `blob_ref`, `mime_type`, `width` and `height`. An item must hold **every required property along the chain**, so a receipt needs a `blob_ref`, a `mime_type` and a `merchant`. A write that lacks one is refused `400 invalid_properties`, naming it.

A type can also set [`display_hints`](/catalog/type-rules#display-hints): the field that names an item, the field that holds its Markdown text, and an `icon`, an Iconify ID such as `lucide:receipt`. Each one comes from the nearest type that sets it, so a receipt that sets only an icon keeps the title its parent names. The server and a device [name an item](/catalog/type-rules#names-and-previews) from these hints by one rule, which a Swift app reads with [`displays(of:)`](/swift-sdk/items#names-and-previews).
A type can also set [`display_hints`](/catalog/display-hints): the field that names an item, the field that holds its Markdown text, and an `icon`, an Iconify ID such as `lucide:receipt`. Each one comes from the nearest type that sets it, so a receipt that sets only an icon keeps the title its parent names. The server and a device [name an item](/catalog/display-hints#names-and-previews) from these hints by one rule, which a Swift app reads with [`displays(of:)`](/swift-sdk/items#names-and-previews).

Once the type is registered, [change or delete it](/build/change-a-type) as your app grows.

Expand Down
54 changes: 54 additions & 0 deletions content/docs/catalog/(types)/display-hints.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: Display hints
description: "How a type says what names an item, where its text is and which icon it shows, how Marfa names an item from them, and the icon the owner chooses."
---

A type's `display_hints` tell an app how to show its items: the field that names an item, the field that holds its text and an icon. [Type rules](/catalog/type-rules) has the rest of what a type you register must meet, and [Define a type](/build/define-a-type) shows how to set them.

## The hints

`display_hints` takes three optional hints:

| Hint | What it holds | Marfa refuses |
| --- | --- | --- |
| `title_field` | The name of the field that names an item. | 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, **in Markdown**. Apps render it, and a device makes [edges](/catalog/edges) from its links. | The same as `title_field`. |
| `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. |

Marfa refuses a hint with `400 invalid_schema`, naming the member, such as `display_hints.icon`.

**Marfa doesn't check the text in `body_field`.** It stores the value as sent and never refuses it as malformed Markdown, so a [connector](/build/connector-pull-and-push) converts a vendor's HTML first, or an app shows the tags.

**The icon sets are fixed because apps carry their own copy**, so an icon draws offline and looks the same in every app. The accepted IDs are in [`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**, so a type that sets only an `icon` keeps the title and body its parent names. `GET /types/{id}` answers each hint after inheritance, and `GET /types` lists only the hints each type sets itself.

## Names and previews

The server and a device name an item by one rule, so they show the same name, offline included.

- **The name** comes from the first of these fields that holds text: `title_field`, `title`, `name`, `body_field`, `body`, `text`, `filename`, `url` and `address`. Marfa takes the first line, collapses each run of whitespace and keeps at most 120 characters, ending in `…` where it cuts.
- **A name taken from `body_field`, `body` or `text` loses its Markdown**, so a note whose body starts `# Plan` is named `Plan`. A name from any other field keeps every character.
- **An item with no name** is `Untitled` to the server. A device answers that it has none, so each app chooses its own word.
- **A preview** is the body field's text without its Markdown, at most 240 characters. Where the name came from the body field, the preview leaves out that line and every line before it. Only a device makes one.

A Swift app reads both with [`displays(of:)`](/swift-sdk/items#names-and-previews).

## The owner's icon

**The owner can choose the icon of any type**, a shipped or connector type included, without replacing it. The choice sits beside the type's definition, so the author's icon stays the default. [Set a type's icon](/api-reference/types/setTypeIcon) and [clear a type's icon](/api-reference/types/clearTypeIcon) are the operations:

```bash
marfa types set-icon user.recipe lucide:chef-hat
marfa types clear-icon user.recipe
```

- **Reads answer it in place of the author's icon**, and a subtype with no icon of its own shows it.
- **Replacing the type keeps it, and deleting the type discards it.** A type registered again under the same identifier starts without one.
- **Devices take it at their next catch-up**, or when a follow next opens its stream. No event announces it.
- **The icon follows the same rules as the author's.** One the sets don't hold is refused `400 invalid_schema`, naming `icon`, and a type nothing registered is refused `404 type_not_found`. A refused call leaves the icon the type had.
- **Who can change it:** the owner, a local process, or a key with `schema.write` and write on the type. A key without `schema.write` is refused `403 forbidden`, and one without write on the type `403 type_not_permitted`.

[Change a type](/build/change-a-type) explains what a replacement does with the owner's icon when you send back what a `GET` answered.

The rules behind this page are in the [types](https://github.com/withmarfa/marfa/blob/main/conformance/spec/types.md) and [device](https://github.com/withmarfa/marfa/blob/main/conformance/spec/device.md) chapters of the contract.
1 change: 1 addition & 0 deletions content/docs/catalog/(types)/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"(core)",
"(system)",
"type-rules",
"display-hints",
"field-kinds"
]
}
Loading
Loading