# The Job Ticket

## The Job Ticket

**Files:** the `ticket:` blocks in your content files, `src/sections.schema.ts` (`sharedTicket`) · **Skill level:** Beginner

<div class="border border-dark">
  ![The summary ticket beside the booking form](/images/homio/book.jpg)
</div>

The **job ticket** is Homio's signature card: a white receipt with a mono number in the header, rows with small square marks, a punched tear line, and a total at the bottom. It shows prices, checklists, and short facts across the site. Some tickets are filled in by the theme (the estimator, the booking summary); others you write in a `ticket:` block in a content file.

### Where Tickets Appear
**Computed by the theme.** You don't type their rows or totals; they come from the pricing file and the service, area, or project files:

| Ticket                                | Where                                                      | What sets it                                                                                                |
| ------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| The estimator                         | `/pricing/`, cleaning service pages, default service pages | The rate cards. `number` in `estimateSection` replaces the computed code                                    |
| "Your ticket" beside the booking form | `/book/`                                                   | The rate cards. `summaryTitle` in `booking-section.md`                                                      |
| The service ticket in the hero        | Service pages                                              | The service file and its rate card. `ticket.rows` in `service-hero-section.md` sets how many task rows show |
| The "Not covered" ticket              | Repair service pages                                       | `notIncluded` in the service file; `ticketLabel` and `footnote` in `not-covered-section.md`                 |
| The area ticket                       | Area pages                                                 | The area file (ZIP codes, crews, phone, hours) and the first rate card                                      |
| The job ticket of a project           | Project pages, recent-job blocks                           | The project file (`ticket`, `crewSize`, `duration`, `scope`, `price`)                                       |

The estimate code in the header (for example `EST-4K7Q2`) is built from the visitor's selection, so the booking form shows the same code the visitor saw in the estimator, and you receive it with the request. Change the `EST` prefix with `ticketPrefix` in `[settings.estimator]` (`src/config/config.toml`).

**Written by you** in a `ticket:` block:

| Ticket                         | File                                                                                                                                                                          |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Page header, `split` layout    | `pageHeader.ticket` in a page file, for example `src/content/guarantee/english/-index.md`, `src/content/careers/english/-index.md`, `src/content/pages/english/gift-cards.md` |
| Call to action, `split` layout | `ticket` in `src/content/sections/english/call-to-action.md`                                                                                                                  |
| 404 page                       | `ticket` in `src/content/sections/english/not-found-section.md`                                                                                                               |
| Booking thank-you page         | `ticket` in `src/content/pages/english/book-thanks.md`                                                                                                                        |
| Policy summary                 | `contentPage.summary` in `src/content/pages/english/cancellation-policy.md` and `terms-conditions.md` (add one to any page that has a `contentPage` block)                    |
| Industry site ticket           | `ticket:` (a list of rows) in `src/content/industries/english/<industry>.md`                                                                                                  |
| In a blog post                 | The `<JobTicket>` component (see below)                                                                                                                                       |

### Ticket Fields
Every written ticket uses the same fields (`sharedTicket` in `src/sections.schema.ts`):

```yaml
ticket:
  number: "2041" # "2041" → "No. 2041"
  label: "Standard clean" # header right: frequency, date, or service
  title: "Standard clean · 2 bed, 1 bath"
  meta: # optional label/value pairs under the title
    - { label: "Crew", value: "2" }
  rows:
    - { type: "check", label: "Same crew every visit" }
    - { type: "line", label: "Late cancellation", value: 40 }
  total:
    label: "To pay"
    value: 0 # a number is money; a string is shown as written ("On quote")
    unit: "per visit"
  footnote: "Sample values. Set your own."
```

| Field      | What it does                                                                                                                    |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `number`   | The mono number in the header. Digits show as "No. 2041". In page headers, a number with letters shows as written ("GUARANTEE") |
| `label`    | Short text on the right of the header                                                                                           |
| `title`    | The ticket heading                                                                                                              |
| `meta`     | Optional label/value pairs under the title                                                                                      |
| `rows`     | The rows (see the row types below)                                                                                              |
| `total`    | The bottom line: `label`, `value`, and an optional `unit`. A number counts up and is formatted as money                         |
| `footnote` | A small line at the bottom                                                                                                      |

### Row Types
Each row has a `label` (inline Markdown is allowed), an optional `detail` (a second, muted line), an optional `value`, and a `type`:

| `type`     | Shows                                                       | Value                               |
| ---------- | ----------------------------------------------------------- | ----------------------------------- |
| `check`    | A ticked row: included. The default when `type` is left out | Optional                            |
| `extra`    | A plus row: an add-on                                       | A number shows as "+$35"            |
| `excluded` | A dashed row: not covered                                   | Empty shows "not covered"           |
| `line`     | A plain label and value                                     | Text or a number                    |
| `discount` | A discount line                                             | A number shows as a negative amount |
| `note`     | A plain note                                                | Optional                            |

**Money or text.** A number `value` is money: it is formatted per language with the currency in `[settings.estimator]` (`40` shows "$40" in English and "40 $US" in French). A string is shown exactly as written (`"up to 24 h before"`, `"$0"`). Use a string when the value is not an amount, or to control the exact text.

`key` is a stable id that some tickets use to fill a row in. For example, on the careers page a row with `key: "pay-range"` and an empty value shows the pay range of your open roles, and on the 404 page the row with `key: "requested"` shows the address the visitor asked for.

### A Computed Total In The Call To Action
The call to action's `split` layout can price its ticket from the rate cards instead of a typed total:

```yaml
ticket:
  title: "Standard clean · 2 bed, 1 bath"
  rows:
    - { type: "check", label: "Same crew every visit" } # sample promise
  # The total is computed from the rate card, as in the estimator (sample rates give $115 per
  # visit). Remove `quote` and set `total: { label, value, unit }` to type it instead.
  quote:
    service: "standard-cleaning" # rate-card id from src/content/pricing/
    home: { bedrooms: 2, bathrooms: 1 }
    frequency: "biweekly" # frequency id from the pricing file; empty = the card's default
```

Prefer `quote` wherever you show a price: it follows your rates. A typed `total` does not.

### In A Blog Post
In an `.mdx` post or page, `<JobTicket>` turns a Markdown list into a ticket. Each list item becomes a check row:

```mdx
<JobTicket title="What our move-out clean covers" code="TIP-014" label="Move-out" service="move-out-cleaning">

- Oven, hob, and extractor filters degreased
- Inside every cabinet and drawer

</JobTicket>
```

- `code` is the ticket number and `label` the header text.
- `service` (a rate-card id) adds that service's "from" price as the total and a "Get a price" button to `/book/?service=…`.
- `total` replaces the price with your own text ("From $220"). `footnote` adds a small line.

Leave a blank line after the opening tag and before the closing tag, or the list is not read. More examples are on the `/components/` page (`src/content/pages/english/components.mdx`).

<Aside type="caution" title="Sample values">
  The ticket numbers, fees, crew sizes, and totals in the shipped `ticket:`
  blocks are samples (marked `# sample`). Replace them, and check that a fee on
  a ticket matches the same fee in your policies and questions.
</Aside>

`/elements/` shows a ticket in every size and color on one page, with a working demo.

### Do It With AI
```text
Update the ticket in the page header of src/content/guarantee/english/-index.md: number
"[TEXT]", label "[TEXT]", rows [ROWS], total "[TEXT]". Use the row types check, line, and
excluded from sharedTicket in src/sections.schema.ts. Write money as a number unless it needs
special text. Do the French file too.
```

```text
Find every `ticket:` block and `contentPage.summary` in src/content (English and French) that
shows a fee or price as typed text or a number, list them with their file, and update them to
match my prices: [FEES AND PRICES].
```