# Studio and the door

Serve Studio, sign in through Lys and use the store, query, graph, history and configuration areas.

Studio is the browser interface. The door serves its built files and carries requests to named store sockets with the signed-in person's pass. The store still decides every protected read and write.

## Before you begin

Have built Studio files, a running store configured with Lys and a Lys app for the door. Register the door's HTTPS callback with that app. Put a TLS proxy in front of the loopback door listener. The app credential stays in a file readable by the door user.

Studio source lives in `surface/studio`. Its `build` script checks TypeScript and builds assets into `dist`. These pages used the existing built assets for route checks. Rebuilding Studio and proving its current source in a browser were not part of the documentation check.

## Serve the door

Write the door file with the public origin, built files, store sockets and Lys details.

**door.json**

```json
{
  "listen": "127.0.0.1:7741",
  "origin": "https://studio.example.au",
  "files": "/opt/haematite/studio",
  "stores": {
    "wards": "/run/haematite/ward.sock"
  },
  "lys": {
    "issuer": "https://lys.example.au",
    "client_id": "haematite-studio",
    "secret_file": "/etc/haematite/lys-app-credential",
    "redirect": "https://studio.example.au/callback"
  }
}
```


**Terminal**

```bash
haematite door --config /etc/haematite/door.json
```


Open the public origin. Studio first asks `/api/haem` for the configured stores. Without a session it sends the browser to `/signin`. Lys signs the person in and returns to `/callback`. The door validates the browser-bound state, exchanges the code and verifies the returned pass for audience `haematite`.

The session cookie is `HttpOnly`, `Secure` and `SameSite=Strict`. Short-lived sign-in cookies use `SameSite=Lax` so the callback can return from Lys. The pass and refresh token stay server-side. A door restart loses its in-memory sessions, so people sign in again.

## What the door serves

`GET /api/session` answers the holder, expiry and refresh status used by Configuration. `GET /api/haem` lists configured store names. Listing a socket does not grant access to its contents.

`POST /api/haem/wards` takes a `method` and `params`, connects to that store, presents the session's pass and carries one verb. The request's Origin must exactly match the configured Studio origin. `GET /api/haem/wards/follow?branch=main` follows head changes as server-sent events. It needs a session, and any supplied Origin must match.

`POST /signout` forgets the server session and clears the browser cookie. It requires the Studio origin and answers 204 even if the session was already gone. It does not revoke the person's other Lys sessions.

Other GET addresses serve built assets or the application. Hashed assets can be cached indefinitely and served with gzip. Keep a complete matching asset set when replacing Studio files.

## Choose the place you are reading

Studio uses hash routes. A link such as `#/data/patients?store=wards&branch=main` names the store and branch. Add `version=generation.sequence` inside the hash query to hold a retained version. Without a version the tab follows the branch head.

An unknown named store or branch is refused. Studio does not silently open another one. A named version is checked against retained history before the screen reads it. Browser preferences choose the initial place only when the address leaves it out.

## Read and change records

**Home**, `#/home`, shows collections, branch counts, store counts and recent versions. A count the service cannot supply is shown with its reason rather than invented.

**Tabular**, `#/data` or `#/data/patients`, reads collections in pages. Open a row to inspect its fields and values. A record address is `#/r/patients/p0`; record tabs keep their own address. Forms live at `#/f/patients/p0`, or omit the id for a new record. Writes carry the version read, so a changed record is refused instead of overwritten.

**Objects**, `#/objects`, defines collection schemas, fields, relationships and record pages. Select an object at `#/objects/patients`. Its page builder is `#/objects/patients/pages/name`; its templates are under `#/objects/patients/templates`. **Drawing**, `#/model`, is the Objects tab for the collection model. [Templates and actions](/haematite/reference/studio-templates-and-actions/) gives their stored declarations and limits.

## Query and search

**Query**, `#/query`, runs checked pipeline or jq text. Read the marked refusal span when a word, argument or stage placement is wrong. Results are bounded pages at a captured version. Inspect the result origin before following it to a record.

**Analytics**, `#/analytics`, keeps named questions, their parameters and runs. **Query modules**, `#/modules`, is its tab for reusable query definitions. A missing or unreadable saved definition is shown by name. **Search**, `#/search`, reads text matches and links them to records. Saved query and view data lives in the store; browser preferences do not replace it.

**Vectors**, `#/vectors`, lists vector fields, their coverage and the record or text question used for nearest records. A supplied-vector field can use a stored vector question. A text question needs a declared embedded field, matching model and ready worker. The [search reference](/haematite/query-language/search/) explains those distinctions.

## Relations and views

**Graph**, `#/graph`, reads relationships and records with bounded graph pages. A collection and record may be named as `#/graph/patients/p0`. Filters, selected types and the query are kept in the graph address. A WebGPU or painting failure is visible; a partial read is not a complete graph. See [Relations and the graph](/haematite/concepts/relations-and-the-graph/).

**Views**, `#/views`, holds named views and their arrangements. `#/p/name` opens a saved page. A view is scoped to a store and branch. A refused or pending save retains its visible state and names the problem; it does not mean that another browser can read the draft.

## Branches and history

**Branches**, `#/branches`, lists branches, their versions and review controls. Read all review pages and pass that captured source version to merge. A merge refuses a moved source or target. **Changes**, `#/activity`, is the Branches tab for comparing retained moments. A reversed comparison shows what would be undone, with additions and removals exchanged.

Use a branch for a change that needs review. Import and pack actions now generate names with a readable stem and a unique suffix, without `/`. Object editing uses a stable name with a digest of the full object name. The new-branch form accepts 1 to 512 ASCII letters, digits, underscores, hyphens or dots. The service still decides whether a name is placeable; a refused name is `branch_name_unplaceable`. This describes the source, rather than a browser run of the changed screens.

## Configuration

**Preferences**, `#/settings`, controls browser choices, read sizes, value drawing and kept work. It also shows the Lys session and offers sign-out. A value above the service maximum remains a preference but reads are limited by the served maximum.

**Stores**, `#/stores`, lists the door's named stores and their protocol, counts and sizes where available. It cannot add a socket or change `service.json`. An administrator changes those files and restarts the relevant process.

**Import and export**, `#/io`, reads CSV, JSON and NDJSON for import onto a branch and exports a collection at one captured version. A cancelled or failed export reports partial progress. A collection export omits the rest of the store and is not a backup.

**Packs**, `#/packs`, lists saved groups of collection schemas. Adding a pack attempts its changes on a branch for review. A missing pack collection gives an empty list, not an installed pack.

The navigation palette, keyboard help and movable utility panels are available across screens. The Assistant panel (⌘I) talks to your store's assistant through the door. From 0.21.0 the door serves the assistant's routes under `/api/assistant/<store>` when its configuration names an `assistant.socket`; without one the panel says the door carries no assistant. [Use the assistant](/haematite/getting-started/use-the-assistant/) covers the panel.

## Read a refusal on screen

`session_unknown` or `session_expired` means sign in again. `sign_in_state_unknown` or `sign_in_state_refused` means begin a fresh sign-in in this browser. `busy_pending_signins` means the pending-state bound is full; complete or restart abandoned sign-ins before trying again.

`origin_refused` means the proxy or browser used the wrong origin. `door_not_reached` means the browser could not complete the door request. `store_unknown` means this door has no configured socket by that name. `permission_refused` names a missing Lys grant. `version_conflict` or `record_changed` means read the current record before deciding how to apply the edit.

`already_run` means the action has a run record for that record version. `not_offered` means the record no longer meets its condition. `action_log_failed` means its run history could not be recorded; read the refusal and current record before repeating the action.

The [refusals reference](/haematite/reference/refusals/) includes the remaining named Studio, transport, storage and worker diagnostics. The diagnostic reference retains library source locations. The scratch door verified files, session routes, callback, forwarding, origin checks and sign-out. The screen descriptions here are source-derived from `App.tsx`, `railItems.ts` and the screen modules, rather than a claim that every screen was exercised.
