# Calendar sources

> A package shows its dated records in the nexxus calendar, next to holidays, vacation and the person's own appointments. How the calendar section of the manifest works, who sees which entries, and how moving an entry writes back to the record.

Many records have a date that people plan around: a deadline, an appointment with a patient, a delivery. nexxus has one [calendar](/use/calendar/) for that, under <MenuPath to="Start > Calendar" />. A package does not build its own. It declares **calendar sources** in its manifest: which records of which entity, by which date fields, for whom, in which colour, and whether people may move them. nexxus reads the records through the regular record search and writes a move through the regular record update, so the package's rights, validation and events apply as for any edit.

<Flow
  steps={[
    { icon: 'file-json', title: 'Manifest', text: 'The package names entity, date fields, title, people and colours in calendar.' },
    { icon: 'search', title: 'Record search', text: 'nexxus asks for the records in the shown days, with the reader\'s rights.', tone: 'cyan' },
    { icon: 'calendar-days', title: 'Calendar', text: 'Shows them beside holidays, vacation and appointments, by Mine or All.', tone: 'lavender' },
    { icon: 'move', title: 'Record update', text: 'A drag writes the new date back, with the record\'s validation and the updated event.' },
  ]}
  caption="The package brings no calendar code. Everything runs through the record API it already has."
/>

## The calendar section of the manifest

`calendar` is a top-level list. Each entry is one source with its own chip in the calendar.

```json
"calendar": [
  {
    "key": "fristen",
    "entity": "frist",
    "label": { "de": "Fristen", "en": "Deadlines" },
    "icon": "event_upcoming",
    "start": "faellig_am",
    "title": "titel",
    "subtitle": ["akte", "typ"],
    "people": ["zustaendig"],
    "colorField": "typ",
    "colors": { "notfrist": "red", "frist": "amber", "vorfrist": "teal" },
    "filter": { "status": "offen" },
    "move": false
  }
]
```

| Field | Required | Meaning |
|---|---|---|
| `key` | yes | Name of the source, unique in the list. A lower-case letter, then lower-case letters, digits, `-` and `_`. |
| `entity` | yes | An entity of the package. |
| `label` | yes | The chip's text, German and English, optionally Polish. |
| `icon` | no | A Material Symbols name. Default: the entity's icon. |
| `start` | yes | A field of type `date` (an all-day entry) or `datetime` (an entry with times), `indexed: true`. |
| `end` | no | A field of the same type as `start`, indexed. Without it a `date` entry lasts one day and a `datetime` entry 30 minutes. An end before the start counts as missing. |
| `title` | yes | A field of type `string`, `text`, `enum` or `ref`. An enum shows its label, a ref the display value of its target. Empty: the record's title. |
| `subtitle` | no | Fields shown smaller under the title, joined with " · ". Any type but `json` and `geo`. |
| `people` | no | `ref` fields with `target: "core:user"`. They decide when a record counts as the reader's own, see below. |
| `colorField` | no | An `enum` field that colours each entry. |
| `colors` | no | With `colorField`: value key to a colour. The first one is also the colour of the source's chip. |
| `color` | no | Without `colorField`: the colour of the whole source. |
| `filter` | no | A fixed filter on indexed fields: a value, or a list meaning "one of". The calendar never shows records outside it. |
| `move` | no | `true` lets people with write access move the entries. Default `false`. |

The colours are tokens, not hex values, so they work in the light and the dark theme: `blue`, `teal`, `green`, `lime`, `amber`, `orange`, `red`, `pink`, `purple`, `slate`. A value without an entry in `colors`, and a source with neither `color` nor `colors`, get a token derived from the value or the source key. The derivation is stable: the same key gives the same colour on every server.

Keep legal deadlines and other dates that must not shift by accident at `move: false`. People still change them on the record page, where the package's own rules apply.

## Who sees what

A source appears for a person when the package's installation is active and the person may read the entity (the permission `<module>.<entity>.read`). Records carry no rights of their own, so whoever may read the entity sees all of its records that pass the source's `filter`. The calendar itself needs the permission `calendar.use`, which every role with employee rights has.

The reader chooses between <Ui>Mine</Ui> and <Ui>All</Ui>:

- **Mine** keeps the records where any of the `people` fields holds the reader. A record with two people fields (responsible and deputy, say) shows for both.
- **All** shows every record the reader may read. With people chosen in the people filter, the calendar keeps the records where a `people` field holds one of them.
- A source **without `people`** shows only under All without a people filter, because none of its records belongs to anyone.

Dates: a `date` is a day without a time zone; an all-day entry's `end` is included. A `datetime` is a point in time and shows in the tenant's time zone (the system setting), so an appointment at 09:30 in Berlin shows at 09:30 for everyone in the tenant. A move writes it back with that zone's offset.

nexxus asks for the records that start in the days on screen and, with an `end`, those that began earlier and still run. It reads at most 2000 records per source and request, 200 at a time. A source cut off there is named on the page: <Ui>there are more entries than the calendar shows at once. A shorter period or a people filter shows them all.</Ui> If your source can have that many records in a month, add a `filter` or `people`.

A click on an entry opens the record page, `/x/<module>/<entity>/<id>`.

## Moving entries

With `move: true`, people who may write the entity (`<module>.<entity>.write`) drag entries to another day or time, and drag the lower edge of an entry with times to change its end. The AI Assistant moves entries the same way, after the person confirms its card.

nexxus then writes the record through the regular update:

- A move writes `start` and, when the record has an end, `end` with the same length. A resize writes only `end` and needs an `end` field.
- Validation runs as on the record page. A `readonly` field cannot be a moved field; the package check refuses that combination.
- The record keeps its history, and the package's service receives `<entity>.updated` like for any edit.
- The request carries the start the person saw. If the record starts elsewhere by now, nexxus answers 409 with the entry as it is now, and the calendar shows that state with the message <Ui>Der Eintrag wurde inzwischen geändert, er beginnt jetzt ...</Ui>

Without `move: true`, or without the write permission, the entry cannot be dragged, and the server refuses a move with 403.

## Rules the package check enforces

`validatePackage` and the upload refuse a package with one message per finding when:

- `entity` does not exist, or a named field is not a field of it, or a `key` appears twice.
- `start` is not `date` or `datetime`, or is not indexed. `end` has another type than `start`, is not indexed, or is the `start` field.
- `move` is `true` and `start` or `end` is `readonly`.
- `title` is not `string`, `text`, `enum` or `ref`; a `subtitle` field is `json` or `geo`, or appears twice.
- A `people` field is not a `ref` to `core:user`, or appears twice.
- `colorField` is not an `enum`, a key in `colors` is not one of its values, `colors` comes without `colorField`, or `color` with it.
- A `filter` field is not indexed, has no value, or a value does not fit its type.

The section is new in the platform. A nexxus version without it refuses the manifest because of the unknown field, so set `platform` to a range that starts at the version that brings the calendar.

## Next steps

- [Calendar](/use/calendar/): what people see and do with your entries.
- [Package structure](/modules/structure/#entities): field types and `indexed`.
- [Data and upgrades](/modules/data-and-upgrades/): which fields are indexed and what a new version may change.