> ## Documentation Index
> Fetch the complete documentation index at: https://help.stralos.app/llms.txt
> Use this file to discover all available pages before exploring further.

# User settings frontend

> stralos-feature-user-settings widget remote — architecture, host contract, and API endpoints

`stralos-feature-user-settings` is the Module Federation widget remote that owns the **Your settings** panel. It is a manifest v2 `kind: widget` remote with `slot: floating-end`. The shell mounts the slot once inside its authenticated layout and passes `widgetProps = { assistantOpen }`; this remote exposes the panel and its provider.

The panel is open to every signed-in member regardless of role. The shell's authenticated layout controls access; the API scopes every read and write to the caller.

## What this remote owns

| Module | Responsibility |
| - | - |
| `src/UserSettingsWidget.tsx` | The always-loaded widget entry. Renders nothing until the shell dispatches `stralos:user-settings-open`, then loads the sheet and opens it. Plain React — no Chakra. |
| `src/sheet/UserSettingsSheet.tsx` | The sheet (ui + Chakra). Loaded on first open via dynamic `import()` and kept mounted after that. Contains all settings sections; currently one: **Default office**. |
| `src/defaultOfficeClient.ts` | The three API calls behind the Default office section: `fetchDefaultOffice`, `fetchOwnActiveOffices`, and `saveDefaultOffice`. |
| `src/api.ts` | `authedFetch` and `apiBase` — the auth and base-URL helpers shared across this remote's API calls. |
| `src/sameOriginBase.ts` | Resolves API base URLs from `__STRALOS_API_URLS__`, falling back to same-origin `/api/<key>`. |
| `src/UserSettingsRemoteProvider.tsx` | A pass-through `RemoteProvider` re-exported from `./widget` so the shell can wrap the slot. Deliberately thin: the sheet mounts `@stralos-co-nz/ui`'s real `RemoteProvider` around itself. |

## Exposed entries

The remote exposes one module:

* `./widget` — the `UserSettingsWidget` component and the `RemoteProvider` + `REMOTE_KEY` the shell wraps the slot with.

## Host contract

### 1. Render the widget in the floating-end slot

Inside the authenticated layout the shell mounts the slot:

```tsx theme={null}
<RemoteProvider remoteKey="userSettings">
  <UserSettingsWidget assistantOpen={assistantOpen} />
</RemoteProvider>
```

The `assistantOpen` prop is passed by the shell's `floating-end` slot to every widget in it. This remote does not use that prop; the shared `SideSheet` resolves its modality when it opens.

### 2. Dispatch the open event

The shell's user menu "Your settings" item dispatches:

```ts theme={null}
window.dispatchEvent(new Event("stralos:user-settings-open"));
```

This is a cross-repository contract. Renaming `USER_SETTINGS_OPEN_EVENT` in this remote without updating the shell leaves the menu item dispatching into silence.

### 3. Widget bundle size

The `./widget` entry is mounted on every chromed page and must remain Chakra-free. The sheet is a dynamic `import()` on first open. `.ci/remote-size-baseline.json` is the accepted gzip sum; `build:remote` fails more than 10% over it.

## API endpoints

All calls are to the Global API (`/api/global`). Authentication follows the `stralos.auth.*` / `window.__STRALOS_AUTH__` contract.

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/global/me/default-office` | Reads the caller's current default office. |
| `PUT` | `/api/global/me/default-office` | Saves or clears the caller's default office choice. Body: `{ locationId: string \| null }`. `null` clears the choice and reverts to automatic. |
| `GET` | `/api/global/locations?owner=ours&status=active&start&length` | Lists the organisation's own active offices for the radio choices. Paged at 200 per page; the client reads up to 10 pages (2,000 offices). |

### Response shapes

`GET /me/default-office` and `PUT /me/default-office` return:

```json theme={null}
{ "locationId": "string | null", "source": "chosen | first-office | none" }
```

`source` values:

* `chosen` — the person has an explicit choice stored.
* `first-office` — no choice stored, or the choice is no longer an active office; the organisation's first-created active office is used.
* `none` — the organisation has no active offices.

A `PUT` of `null` clears the choice, returning the caller to the `first-office` or `none` source. A `422` with the code `invalid_default_office` means the chosen office can no longer be used (it may have been retired); the sheet shows a message asking the person to pick another.

Every response is parsed field by field. A shape the client does not recognise is treated as a failure rather than silently accepted.

## Default office sheet behaviour

The sheet re-reads the default office on every open so it reflects changes made elsewhere (the Locations screen or an admin in Settings → Users) since the last open.

| State | Sheet behaviour |
| - | - |
| Loading | Skeleton choices are shown. |
| Ready | Radio cards list each active office, plus an **Automatic** option that selects `null`. The current choice is pre-selected. |
| Load failed (network or server error) | Error state with a **Try again** button. Nothing has changed. |
| Load failed (404) | The endpoint is not yet deployed. A note says the setting is still on its way; new projects use the company's first office in the meantime. |
| Save failed (422 `invalid_default_office`) | Inline message: the office can't be used any more; pick another and save again. |
| Save failed (other) | Inline message: couldn't save; check connection and try again. |

The **Save** button is disabled until the in-panel choice differs from the stored choice. After a successful save, a live region (visible and announced to screen readers) confirms the saved office name, or says the choice is automatic and names the current stand-in office.

If the sheet is closed with unsaved changes, `SideSheet`'s `confirmOnDirtyClose` shows a confirmation dialog. The record name in that dialog is `Default office`.

## Federation contract

This remote follows the thin-host contract (PRD D1/D3). Host-provided singletons are `react`, `react-dom`, `react-router-dom`, and `react/jsx-runtime`, declared as caret ranges in `.ci/shell-federation-contract.json`. `@stralos-co-nz/ui` and `@stralos-co-nz/domain` are exact pins in `package.json` and are bundled into the remote — they are not shell-provided singletons.

`src/widget.tsx` opens with four specifier-less imports (`import "react"` etc.) that force `@module-federation/vite` to keep each `import: false` share's full export surface. Deleting them makes the build intermittently red. See the file header and `src/shareGuard.test.ts` for the mechanism.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.