# Changelog — `@ai-matrx/meet`

## 0.11.124

UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/meet/src`.

## 0.11.123

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff a2ff12b279 f75d04e431 -- apps/shared/meet/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.11.122

- **Device changes go through the one watcher.** `createDeviceManager` re-lists on `devicechange` via `@ai-matrx/browser-audio`'s `watchMediaDevices`, and the supervisor lists via `listMediaDevices`; no own listener. Consumer action: none.

## 0.11.121

- **`useCalls` no longer re-renders every second.** The 1s tick re-rendered every consumer (including the app-wide idle `<IncomingCallHost/>`) forever; it now forces a render only when the incoming/outgoing invites changed. Consumer action: none.

## 0.11.120

- **Device listing and the call level meter use `@ai-matrx/browser-audio`** (`listMediaDevices`, `createStreamLevelMeter` with `measure: "peak"`): no private `enumerateDevices` sort or AnalyserNode left. New dependency on `@ai-matrx/browser-audio`. Consumer action: none.

## 0.11.119

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4d24e77604 a6c22763f1 -- apps/shared/meet/src`

Changed shipped source files (2):
- `apps/shared/meet/src/core/__snapshots__/normalize-meet-error-keeps-its-answers.test.ts.snap`
- `apps/shared/meet/src/core/errors.ts`

## 0.11.118

- **Glyph path data removed; the same exports now alias the one glyph home (one-canonical, glyph lane, 2026-10-21).** `skins/meet/icons.tsx` defines no SVG any more: each export is `@ai-matrx/design-system/icons`'s glyph with this package's historical size and stroke. Names, props and sizes are unchanged; `SettingsIcon` and `UndoIcon` now draw lucide `settings` and `undo-2`. Consumer action: none.

## 0.11.117

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4d90c08808 85618acf17 -- apps/shared/meet/src`

Changed shipped source files (1):
- `apps/shared/meet/src/core/api.ts`

## 0.11.116

- **A stale meeting edit is PT409, never 40001 (2026-10-21).** `communication.meet_update_meeting` raised 40001 for a version mismatch; PostgREST retries 40001 without limit, so a deliberate refusal loops forever (it took the database down that day via another function). It now raises PT409, and `normalizeMeetError` reads PT409 as the conflict. Consumer action: none; update the package.
- **US spelling: the British spelling of "color" no longer exists in this package (Arman, 2026-10-21).** Every label, template string, comment, identifier, CSS class/variable and test name that spelled it the British way now says color. Guard: `design-system/src/us-spelling.test.ts` scans all of `apps/` for the word. Consumer action: none for imports; if you matched on a British-spelled identifier, class or CSS variable from this package, use the `color` spelling; update the package.
- **Shared code ships once per side (2026-10-18).** Built `splitting: false`, the root and `/core` each carried the same 43 modules (two copies of every module-level value) and the client entries (`/react`, `/host-slot`, `/skins/meet`) shared 35 more. ESM now builds root + `/core` in one split pass (`chunk-server-*.js`, no banner) and the client entries in another (stamped). CJS unsplit; the one declaration pass unchanged. ESM JS 1,640,804 -> 1,043,954 bytes; `/react` + `/skins/meet` together 924 KB -> 665 KB (`/skins/meet` alone 274 -> 618 KB, since it now shares `/react`'s chunks); every entry's export names identical (ESM and CJS). Guard: `../scripts/check-ships-once.mjs`, run in `build`. Consumer action: none.

## 0.11.115

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 0eb2f8603b 52fc93be02 -- apps/shared/meet/src`

Changed shipped source files (1):
- `apps/shared/meet/src/styles.css`

## 0.11.114

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 1c52fd1b8c 0eb2f8603b -- apps/shared/meet/src`

Changed shipped source files (1):
- `apps/shared/meet/src/styles.css`

## 0.11.113

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff e627b23244 1c52fd1b8c -- apps/shared/meet/src`

Changed shipped source files (13):
- `apps/shared/meet/src/core/errors.ts`
- `apps/shared/meet/src/core/format.ts`
- `apps/shared/meet/src/core/invitations.ts`
- `apps/shared/meet/src/core/phase.ts`
- `apps/shared/meet/src/core/repository.ts`
- `apps/shared/meet/src/core/speaking-detector.ts`
- `apps/shared/meet/src/core/tokens.ts`
- `apps/shared/meet/src/core/types.ts`
- `apps/shared/meet/src/react/hooks.ts`
- `apps/shared/meet/src/react/skin-root.tsx`
- `apps/shared/meet/src/skins/meet/components.tsx`
- `apps/shared/meet/src/skins/meet/icons.tsx`
- ...and 1 more

## 0.11.112

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 039374c694 e627b23244 -- apps/shared/meet/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.11.111

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 51a788b7e7 039374c694 -- apps/shared/meet/src`

Changed shipped source files (4):
- `apps/shared/meet/src/react/calls-ui.tsx`
- `apps/shared/meet/src/skins/meet/collab.tsx`
- `apps/shared/meet/src/skins/meet/components.tsx`
- `apps/shared/meet/src/skins/meet/icons.tsx`

## 0.11.110

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f5ca2d34e0 51a788b7e7 -- apps/shared/meet/src`

Changed shipped source files (12):
- `apps/shared/meet/src/core.ts`
- `apps/shared/meet/src/core/after.ts`
- `apps/shared/meet/src/core/ai.ts`
- `apps/shared/meet/src/core/collab.ts`
- `apps/shared/meet/src/core/errors.ts`
- `apps/shared/meet/src/core/meetings.ts`
- `apps/shared/meet/src/core/repository.ts`
- `apps/shared/meet/src/core/tokens.ts`
- `apps/shared/meet/src/react/calls-ui.tsx`
- `apps/shared/meet/src/react/record.tsx`
- `apps/shared/meet/src/skins/meet.ts`
- `apps/shared/meet/src/skins/meet/components.tsx`

## 0.11.109
### Unreleased

- Page-load markers: `performance.mark("load:<kind>:<name>")` at module top level / first mount — heavy Meet calls UI (home: /meet, /meetings); lazy meet invites read. Native API, no import and no build edge; the host's page-load trace (ai-matrx `lib/diagnostics/loadTrace.ts`) prints them and `pnpm check:smoke` fails a heavy one that evaluates before the person engages. Consumer action: none.

## 0.11.108

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 32a095acb1 ac868e50ec -- apps/shared/meet/src`

Changed shipped source files (1):
- `apps/shared/meet/src/core/ai.ts`

## 0.11.107

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 462dc83f84 32a095acb1 -- apps/shared/meet/src`

Changed shipped source files (5):
- `apps/shared/meet/src/core/calls.ts`
- `apps/shared/meet/src/core/channels.ts`
- `apps/shared/meet/src/core/meetings.ts`
- `apps/shared/meet/src/react/hooks.ts`
- `apps/shared/meet/src/react/provider.tsx`

## 0.11.106

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8b9cb7a612 462dc83f84 -- apps/shared/meet/src`

Changed shipped source files (2):
- `apps/shared/meet/src/core/session-identity.ts`
- `apps/shared/meet/src/core/testing/session.ts`

## 0.11.105

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4d6a83481c 8b9cb7a612 -- apps/shared/meet/src`

Changed shipped source files (2):
- `apps/shared/meet/src/skins/meet/collab.tsx`
- `apps/shared/meet/src/skins/meet/components.tsx`

## 0.11.104

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.103..npm/meet/v0.11.104 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.103

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.102..npm/meet/v0.11.103 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.102

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.101..npm/meet/v0.11.102 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.101

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.100..npm/meet/v0.11.101 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.100

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.99..npm/meet/v0.11.100 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.99

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.98..npm/meet/v0.11.99 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.98

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.97..npm/meet/v0.11.98 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.97

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.96..npm/meet/v0.11.97 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.96

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.95..npm/meet/v0.11.96 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.95

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.94..npm/meet/v0.11.95 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.94

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.93..npm/meet/v0.11.94 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.93

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.92..npm/meet/v0.11.93 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.92

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.91..npm/meet/v0.11.92 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.91

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.90..npm/meet/v0.11.91 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.90

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.89..npm/meet/v0.11.90 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.89

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.88..npm/meet/v0.11.89 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.88

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.87..npm/meet/v0.11.88 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.87

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.86..npm/meet/v0.11.87 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.86

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.85..npm/meet/v0.11.86 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.85

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.84..npm/meet/v0.11.85 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.84

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.83..npm/meet/v0.11.84 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.83

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.82..npm/meet/v0.11.83 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.82

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.81..npm/meet/v0.11.82 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.81

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.80..npm/meet/v0.11.81 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.80

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.79..npm/meet/v0.11.80 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.79

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.78..npm/meet/v0.11.79 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.78

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.77..npm/meet/v0.11.78 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.77

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.76..npm/meet/v0.11.77 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.76

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.75..npm/meet/v0.11.76 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.75

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.74..npm/meet/v0.11.75 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.74

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.73..npm/meet/v0.11.74 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.73

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.72..npm/meet/v0.11.73 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.72

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.71..npm/meet/v0.11.72 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.71

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.70..npm/meet/v0.11.71 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.70

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.69..npm/meet/v0.11.70 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.69

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.68..npm/meet/v0.11.69 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.68

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.67..npm/meet/v0.11.68 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.67

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.66..npm/meet/v0.11.67 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.66

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.65..npm/meet/v0.11.66 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.65

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.64..npm/meet/v0.11.65 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.64

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.63..npm/meet/v0.11.64 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.63

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.62..npm/meet/v0.11.63 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.62

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.61..npm/meet/v0.11.62 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.61

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.60..npm/meet/v0.11.61 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.60

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.59..npm/meet/v0.11.60 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.59

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.58..npm/meet/v0.11.59 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.58

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.57..npm/meet/v0.11.58 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.57

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.56..npm/meet/v0.11.57 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.56

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.55..npm/meet/v0.11.56 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.55

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.54..npm/meet/v0.11.55 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.54

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.53..npm/meet/v0.11.54 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.53

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.52..npm/meet/v0.11.53 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.52

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.51..npm/meet/v0.11.52 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.51

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.50..npm/meet/v0.11.51 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.50

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.49..npm/meet/v0.11.50 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.49

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.48..npm/meet/v0.11.49 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.48

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.47..npm/meet/v0.11.48 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.47

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.46..npm/meet/v0.11.47 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.46

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.45..npm/meet/v0.11.46 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.45

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.44..npm/meet/v0.11.45 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.44

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.43..npm/meet/v0.11.44 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.43

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.42..npm/meet/v0.11.43 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.42

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.41..npm/meet/v0.11.42 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.41

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.40..npm/meet/v0.11.41 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.40

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.39..npm/meet/v0.11.40 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.39

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.38..npm/meet/v0.11.39 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.38

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.37..npm/meet/v0.11.38 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.37

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.36..npm/meet/v0.11.37 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.36

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.35..npm/meet/v0.11.36 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.35

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.34..npm/meet/v0.11.35 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.34

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.33..npm/meet/v0.11.34 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.33

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.32..npm/meet/v0.11.33 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## S7 — identity: one person, one connection per tab (next release)

- The LiveKit identity is now `<person key>~<tab id>` (CORE-DESIGN §2.1). Each page claims a tab
  id at load (`core/tab-claim.ts`: Web Locks + BroadcastChannel) — a refresh reuses it, a
  duplicated tab mints its own, so a second tab never evicts the first.
- A person already in the meeting on another tab or device gets the meeting's choice on the
  pre-join (`snapshot.secondDevice`; Meet: Switch here / Join here too, Teams: Transfer here /
  Add this device). `JoinRequest.secondDevice` answers it; "join here too" never opens the mic.
- Roster tiles are PEOPLE: connections merge by `personOf` (the one identity helper,
  `core/names.ts`); a guard test fails on any identity prefix test elsewhere.
- A tab displaced by "Switch here" reads its exit with the pass it held (read before the room
  is dropped), so it lands on `superseded`, never `removed`.
- `RoomToken` gains `connectionIdentity`, `connectionKind`, `secondDeviceChoice` (optional).

**Consumer action:** none required. A host app drawing its own pre-join reads
`useRoom().secondDevice` and calls `join({ ..., secondDevice })`.

## 0.11.32

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.31..npm/meet/v0.11.32 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.31

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.30..npm/meet/v0.11.31 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.30

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.29..npm/meet/v0.11.30 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.29

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.28..npm/meet/v0.11.29 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.28

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.27..npm/meet/v0.11.28 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.27

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.26..npm/meet/v0.11.27 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.26

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.25..npm/meet/v0.11.26 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.25

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.24..npm/meet/v0.11.25 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.24

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.23..npm/meet/v0.11.24 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.23

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.22..npm/meet/v0.11.23 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.22

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.21..npm/meet/v0.11.22 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.21

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.20..npm/meet/v0.11.21 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.20

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.19..npm/meet/v0.11.20 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.19

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.18..npm/meet/v0.11.19 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.18

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.17..npm/meet/v0.11.18 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.16..npm/meet/v0.11.17 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.16

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.15..npm/meet/v0.11.16 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.14..npm/meet/v0.11.15 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.13..npm/meet/v0.11.14 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.12..npm/meet/v0.11.13 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.11..npm/meet/v0.11.12 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.10..npm/meet/v0.11.11 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.9..npm/meet/v0.11.10 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.8..npm/meet/v0.11.9 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.7..npm/meet/v0.11.8 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.6..npm/meet/v0.11.7 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.5..npm/meet/v0.11.6 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.4..npm/meet/v0.11.5 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.3..npm/meet/v0.11.4 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.2..npm/meet/v0.11.3 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.1..npm/meet/v0.11.2 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.11.0..npm/meet/v0.11.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.11.0

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.7..npm/meet/v0.11.0 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.6..npm/meet/v0.10.7 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.5..npm/meet/v0.10.6 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.4..npm/meet/v0.10.5 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.3..npm/meet/v0.10.4 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.2..npm/meet/v0.10.3 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.1..npm/meet/v0.10.2 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.10.0..npm/meet/v0.10.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.10.0
### Unreleased
### S4: admission truth — the wait reads the server

- **The waiting room reads `GET /v1/meet/state`** (`MEET_ROUTES.state`) on a timer
  (`waiting_poll_seconds`), on every `mx.state.changed` hint, on `online` and on visibility
  return. `snapshot.waiting` holds the row's word; `useWaiting()` exposes it with `withdraw()`
  and `askAgain()`. New phases rendered: `waiting:host_not_started`, `denied`, `knock_expired`,
  `left` (withdrawn); `data-meet-host-present` while waiting.
- **Admission RPCs** `meet_admit`, `meet_deny`, `meet_undeny`, `meet_admit_all` return rows
  (`repository.decideAdmission`, `repository.admitAll`); `MeetingSession.undeny`.
- **A token that cannot subscribe never renders the in-call screen.**
- **Removed (no shim):** `MEET_EVENTS.knock`, `.admission`, `.lock`, `.recording`;
  `MeetingSession.knock`, `useLobby().knock`, the `onDenied` option, `RPCS.admit =
  meet_admit_participant` (dropped from the database). `MEET_EVENTS.stateChanged` is the one
  room-channel event. `AdmissionState` widens to the server's eight states.

Consumer action: none for `<MeetingSkinRoot>` consumers; a custom waiting screen reads
`useWaiting()`.

## 0.9.0
### S3: the meeting pass, the role/standing vocabulary, refusal phases

- **The meeting pass.** `RoomToken` gains `meetingPass`, `meetingPassExpiresAt`, `sessionId`,
  `standing`. Every aidream meet route (record, claim, chat, collab, moderate, Q&A, captions)
  now reads `x-meet-pass` (`MEETING_PASS_HEADER`, `meetingPassHeaders`); the LiveKit `token` is
  for media only. `ROOM_TOKEN_HEADER` is removed (no shim). `rememberedMeetingPass(room)` reads
  a guest's kept pass.
- **Role vocabulary** `host · cohost · presenter · participant · viewer`; being a guest is the
  STANDING `guest` (`ParticipantStanding`, `isLinkFollower`, attribute
  `matrx.participant.standing`). `data-meet-role` still renders `guest` for a link follower.
- **Refusals are phases.** `MeetError.reason` carries the join gate's reason code; a refused
  join renders `refused:<reason>` (`ended` renders the `ended` phase), never pre-join.
- **Session-aware record:** `loadGuestMeetingRecord({ sessionId })`.
- **Removed:** `RPCS.endMeeting` / `repository.endMeeting` (the `meet_end_meeting` RPC is
  dropped; `/v1/meet/end` is the one end door).

Consumer action: send `meetingPass` (not `token`) as `x-meet-pass`; read the guest badge from
`standing`, not `role === "guest"`; replace `ROOM_TOKEN_HEADER` with `MEETING_PASS_HEADER`.

## 0.8.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.8.0..npm/meet/v0.8.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.8.0 — session identity and global slots have one home

- The session reader is `@ai-matrx/data/session-identity` (meet supplies its words and branded
  `UserId`); test helpers come from `@ai-matrx/data/testing`; global slots use
  `@ai-matrx/kit/global-slot` with unchanged keys (`ai-matrx.meet.*`).
- New dependency: `@ai-matrx/data` (architecture step 11).

Consumer action: none.

## 0.7.85
### Unreleased

**The headless boundary (Meet duplicates CORE-DESIGN step S1). Breaking for imports; behavior unchanged.**

- New subpaths: `@ai-matrx/meet/core` (`.` re-exports it) and `@ai-matrx/meet/skins/meet`.
- Today's in-call UI is **Meet skin v0** under `src/skins/meet/`, reading the core only through the
  facade (`useMeetCore()` + headless re-exports), enforced by `scripts/check-skin-imports.mjs`
  (fails the build; self-test plants a bad import in a temp copy).
- `<MeetingRoom>` → `<MeetingSkinRoot>` (same props). The in-call parts (`ControlBar`,
  `ParticipantTile`, `Captions`, `ConsentNotice`, `AttendanceNotice`, `RecordingIndicator`,
  `HostMenu`, `Stage`, panels, icons, collab panels) moved from `/react` to `/skins/meet`.
  `/react` keeps the provider, hooks, `useMeetCore`, `MeetRoot`, the record surfaces
  (`MeetingRecordView`, `RecordingPlayer`, `GuestRecordAccessControl`) and the ringing surface
  (`IncomingCallHost`, `IncomingCallCard`).
- Collab/caption hook logic moved into the core (`availableActivities`, `activityAttention`,
  `performCollab`, `moveToRoom`, `untranslatedLines`, `nextTranslationBatch`,
  `setCaptionLanguage`, `takePanelRequest`, `takeMoveRequest`); the hooks are thin selectors.
  `MEET_REACTIONS` is a core export.
- Deleted (no consumer): `MeetingList`, `ScheduleMeeting`, `CallButton`.
- The provider's React context now lives in the globalThis slot so `/react` and `/skins/meet`
  bundles share it.

**Consumer action:** `MeetingRoom` → `MeetingSkinRoot` from `/react`; import in-call parts and icons
from `@ai-matrx/meet/skins/meet`.

## 0.7.84
### Unreleased

- **Observation contract (Meet duplicates CORE-DESIGN step S0).** Every screen `<MeetingRoom>`
  renders now sits inside ONE `[data-meet-root]` carrying the HARNESS-CONTRACT values derived from
  today's store (phase, role, connection, lobby count, recording, camera/microphone). Tiles carry
  `[data-meet-participant]`, lobby rows `[data-meet-lobby-entry]`, the listen-only notice its
  `data-meet-notice` code. New `<MeetRoot>` / `useCoreRootProps()` / `useCoreObservation()` for a
  consumer's own screens around the room, pure `observeMeeting()` / `rootAttributes()` in the core,
  and `window.__matrxMeet` diagnostics. The wrapper is `display: contents` — no layout change.

**Consumer action:** wrap any meeting screen you draw yourself (link resolution, a guest-name step,
a custom in-call layout) in `<MeetRoot>`, passing `phase` when no store exists yet.

## 0.7.83

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.82..npm/meet/v0.7.83 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.82

- Screen share: a SYSTEM or POLICY block (Chrome/Edge "Permission denied by system", a
  permissions-policy / `display-capture` block, SecurityError) now shows a short notice with the
  remedy (macOS Screen Recording, administrator) and the person stays in the call. A plain
  `NotAllowedError` is a closed picker in Chrome but is indistinguishable from a block in Firefox and
  Safari, so the first one stays silent and the second in a row shows the same notice. New
  `classifyScreenShareError`; `isScreenShareCancel` is now true only for the ambiguous case.
- Guards: `in-call-phase-guard.test.ts` fails if any action other than join / rejoin / moveToBreakout
  can call `fail()`; the real `supportsScreenShare` browser check is tested in `livekit-driver.test.ts`.

**Consumer action:** none.

## 0.7.81

**Three live defects, one class: an in-call action never changes the call phase.**

- Screen share: closing the browser's picker is silent (the person stays in the call, no
  notice); a real failure shows a short notice through the room's existing action toast. The
  engine no longer stamps `failed` for an in-call action, which had thrown a live meeting back to
  "Ready to join?" with a camera/microphone sentence and left a stale tile on the host's grid.
  `RoomEngine.supportsScreenShare()` / `useRoom().canShareScreen` / optional driver
  `supportsScreenShare()` — the Share screen control is absent where the browser has no
  `getDisplayMedia` (iOS). New `isScreenShareCancel`, `normalizeScreenShareError` in `core/errors`.
- `isListenOnly` / `listenOnlyNotice`: the "attending without a microphone" banner is only for a
  microphone that was REFUSED or missing (`unavailable !== null`). Muting, or joining muted by
  choice, no longer shows it.
- `PhoneOffIcon` redrawn as the filled call-end handset (the old drawing had a wrong-way slash and
  a garbage path). New `react/icons.test.tsx` validates every icon's geometry.

**Consumer action:** none. A host rendering its own banner from `isListenOnly` now gets `false`
for a self-mute.

## 0.7.80

- `attendanceCsv` writes through THE one writer, `@ai-matrx/kit/delimited` (AP-2): CRLF records and trailing CRLF unchanged; a name that opens like a formula gets a leading `'`.

**Consumer action:** none.

## 0.7.79

**A meeting's record is read once per tab, and kept live.** `useMeetingRecord` held the bundle
in its own state and read all four parts (`meet_participants`, `meet_notes`,
`meet_transcript_segments`, `meet_recordings`) on every mount — so a board tile that slept
and woke (React `<Activity>` re-runs every effect), a Remove + Undo, and every second view of
the same meeting read the whole record again. The record now lives in one per-tab cache
(`core/record-cache.ts`, keyed by reader + meeting): the first view reads, later views and
remounts render the kept record and read nothing; a read in flight is joined; Retry and a
reconnect read again, and an older answer never paints over a newer one. A member's record is
kept current by ONE change-feed channel per meeting (notes, transcript, attendance,
recordings — applied row by row, no re-read), held while any view is mounted and 60 s after;
past that the record is marked stale and the next view reads it once. A meeting rename
re-assembles the kept record instead of re-reading it. New exports: `loadMeetingRecordParts`,
`assembleMeetingRecord`, `createMeetingRecordCache`, `MEETING_RECORD_GRACE_MS`, and
`meetingRecordCache()` from `/react`. Guard: `react/meeting-record-reads-once.test.tsx`
(4/4 red on 0.7.78).

Consumer action: none. A host test that renders the record for the same meeting twice and
expects two reads resets the cache between cases (`meetingRecordCache().reset()`).

## 0.7.78

**A meeting can run inside a box.** `.mx-meet` was `height: 100dvh`, so a stage mounted in a
board tile or a pane was as tall as the window and its control bar fell below the box. The
height now reads `var(--mx-meet-height, 100dvh)`: unchanged by default; a host sets
`--mx-meet-height: 100%` on an ancestor with a definite height to contain every screen.
Guard: `styles.test.ts` "the stage's height".

Consumer action: none (opt-in).

## 0.7.77

The pre-join background choice spans its column (`SegmentedControl fullWidth`) instead of
leaving an empty grey track to the right of three left-packed options.

Consumer action: none.

## 0.7.76

**The pre-join lobby is the design system, not a second one.** Before this, the
lobby rendered native `<select>`, `<input type="checkbox">` and pill `<button>`s
styled by this package's own CSS, so the first screen of every meeting looked
like a different product, and its settings column was `0.8fr` of the screen
(≈1,000px wide on a 2560px display, Join button stranded at the far edge).

- Device pickers (`DevicePicker`, also used in the in-room settings) are the
  design-system `Select`; noise suppression and the join toggles are `Checkbox`
  + `Label`; the background choice is `SegmentedControl`; Test and Join now are
  `Button`; the recording-consent notice is `Alert` (`info`).
- Layout: the settings column holds at 300–400px and the composition centres
  inside 1,200px.
- Guard: `components.test.tsx` "the pre-join lobby renders design-system
  controls, never native ones" (fails on 0.7.75, passes here).

Consumer action: none.

## 0.7.75

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.73..npm/meet/v0.7.75 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.74

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.73..npm/meet/v0.7.74 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.73

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.72..npm/meet/v0.7.73 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.72

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.71..npm/meet/v0.7.72 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.71

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.70..npm/meet/v0.7.71 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.70

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.69..npm/meet/v0.7.70 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.69

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.68..npm/meet/v0.7.69 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.68

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.67..npm/meet/v0.7.68 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.67

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.66..npm/meet/v0.7.67 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.66

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.65..npm/meet/v0.7.66 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.65

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.64..npm/meet/v0.7.65 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.64

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.63..npm/meet/v0.7.64 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.63

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.62..npm/meet/v0.7.63 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.62

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.61..npm/meet/v0.7.62 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.61

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.60..npm/meet/v0.7.61 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.60

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.59..npm/meet/v0.7.60 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.59

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.58..npm/meet/v0.7.59 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.58

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.57..npm/meet/v0.7.58 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.57

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.56..npm/meet/v0.7.57 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.56

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.55..npm/meet/v0.7.56 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.55

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.54..npm/meet/v0.7.55 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.54

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.53..npm/meet/v0.7.54 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.53

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.52..npm/meet/v0.7.53 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.52

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.51..npm/meet/v0.7.52 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.51

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.50..npm/meet/v0.7.51 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.50

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.49..npm/meet/v0.7.50 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.49

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.48..npm/meet/v0.7.49 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.48

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.47..npm/meet/v0.7.48 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.47

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.46..npm/meet/v0.7.47 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.46

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.45..npm/meet/v0.7.46 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.45

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.44..npm/meet/v0.7.45 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.44

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.43..npm/meet/v0.7.44 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.43

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.42..npm/meet/v0.7.43 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.42

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.41..npm/meet/v0.7.42 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.41

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.40..npm/meet/v0.7.41 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.40

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.39..npm/meet/v0.7.40 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.39

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.38..npm/meet/v0.7.39 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.38

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.37..npm/meet/v0.7.38 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.37

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.36..npm/meet/v0.7.37 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.36

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.35..npm/meet/v0.7.36 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.35

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.34..npm/meet/v0.7.35 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.34

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.33..npm/meet/v0.7.34 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.33

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.32..npm/meet/v0.7.33 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.32

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.31..npm/meet/v0.7.32 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.31

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.30..npm/meet/v0.7.31 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.30

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.29..npm/meet/v0.7.30 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.29

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.28..npm/meet/v0.7.29 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.28

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.27..npm/meet/v0.7.28 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.27

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.26..npm/meet/v0.7.27 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.26

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.25..npm/meet/v0.7.26 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.25

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.24..npm/meet/v0.7.25 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.24

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.23..npm/meet/v0.7.24 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.23

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.22..npm/meet/v0.7.23 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.22

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.21..npm/meet/v0.7.22 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.21

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.20..npm/meet/v0.7.21 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.20

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.19..npm/meet/v0.7.20 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.19

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.18..npm/meet/v0.7.19 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.18

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.17..npm/meet/v0.7.18 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.15..npm/meet/v0.7.17 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.16
### Unreleased

- **The recording clock and the breakout countdown are kit's `formatDurationMs` clock** (package-twins drain, 2026-09-30): `clockPosition` and the breakout timer delegate; `9:04` and `1:02:33` unchanged, and a breakout over an hour reads `1:15:00` (was `75:00`). The WebVTT cue stamp stays local on purpose (a caption-file grammar, not display words). Consumer action: none.

## 0.7.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.14..npm/meet/v0.7.15 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.13..npm/meet/v0.7.14 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.12..npm/meet/v0.7.13 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.11..npm/meet/v0.7.12 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.11
### Meet wave 5 (next minor)

- **Polls** (Zoom/Meet parity): a host or co-host creates single- or multiple-choice polls,
  anonymous or named, saves a draft or launches; everyone votes (and may change their vote);
  hosts see live counts (and, for a named poll, who picked what); everyone sees counts once the
  host shares them; end, hide, delete. A launched poll opens the Polls panel for participants.
- **Q&A**: ask (anonymously when the organization allows), upvote, sort popular/recent; a host
  answers (typed or out loud), dismisses (hosts-only afterwards) or reopens; askers delete their own.
- **Shared whiteboard**: one board per meeting that takes the stage while open — pen, highlighter,
  six inks, erase a stroke (yours; a host erases any), undo your last, a host clears (archived).
- **Breakout rooms**: a host opens N rooms — automatically, manually, or letting people choose —
  with a timer; broadcasts to every room; moves one person; visits any room; brings everyone back.
  Each breakout is its own LiveKit room minted by the server (`RoomEngine.moveToBreakout`,
  `TokenRequest.breakoutRoomId`); a person sees their assignment with Join, the countdown, and
  Return to main room; the timer closes rooms when the server's clock agrees.
- **Translated captions**: Settings → "Translate captions to" (starts at the organization's
  `meet.caption_translation_language`, else the viewer's language); the translated line shows
  with the words as spoken beneath it. Refusals (no translator assigned) are said, never hidden.
- **Fix (production walk, 2026-09-29):** a host now sees a new question the moment it is asked —
  hosts dropped every room copy of a poll/question expecting a fuller hosts' copy, which the
  server sends only for polls and dismissed questions. Every copy is folded now; a count-less
  room copy of a poll keeps the host's live counts. Guard: `collab.test.ts` › "a host sees a new question…".
- One door for all four: the **Activities** control in the control bar (absent when the
  organization's knobs turn everything off). The Settings panel is titled "Settings".
- New API: `core/collab.ts` (`CollabSnapshot`, `applyCollabPacket`, `loadMeetingActivities` for
  a record page, …), `InRoomClient.loadCollab / collab / translateCaptions`, room event
  `ROOM_EVENTS.collab` (`matrx.meet.collab`), `MEET_ROUTES.collab` / `captionsTranslate`,
  `MeetSnapshot.collab` / `breakoutRoomId`; React: `ActivitiesControl`, `PollsPanel`, `QaPanel`,
  `WhiteboardPanel`, `BreakoutsPanel`, `BreakoutBanner`, `CaptionLanguageSettings`, `useCollab`.
- Needs aidream with `POST|GET /v1/meet/collab` and `POST /v1/meet/captions/translate`.
- Guards: `core/collab.test.ts`, `react/collab.test.tsx`.
- **Consumer action:** none for the room (it mounts everything); a record page may render
  `loadMeetingActivities` results.

## 0.7.10
### Unreleased
### (next patch)

- **Fix — people in a meeting can HEAR each other** (production re-verification, 2026-09-29):
  `livekit-client` never plays a remote audio track by itself, and nothing in this package
  attached one, so every meeting received everyone's audio and played none of it. The LiveKit
  driver now attaches every subscribed remote microphone to a hidden audio element in the
  document (not tied to tiles, so a person on stage page two or in the Board overflow is still
  heard) and removes it on unsubscribe and on disconnect.
- **Fix — a person admitted from the waiting room is no longer a black tile** for everyone
  already inside: a tile rendered before its track was subscribed attached nothing and never
  retried. `attach` now keeps listening for that participant's track and (re)attaches when it
  is subscribed or republished; the returned cleanup stops listening.
- **Fix — link guests read "Guest" on the attendance report**, not "Member": the durable row
  stores them as `participant` with no account, so "no account" now decides it.
- Guards: `src/core/livekit-driver.media.test.ts` (4 of 6 failed before the fix),
  `after.test.ts` › "a link guest is a GUEST as the database stores them".
- **Consumer action:** none.

## 0.7.9
### (next patch)

- **Fix — a database refusal reads as a sentence for a person** (verifier, 2026-09-27, the
  class behind raw toast text): `normalizeMeetError`'s `message` was
  "meet.x: denied by the database (42501: …)" with a remedy about RLS and service-role
  clients, and hosts render `message` + `remedy` in toasts. Now our RPC's own sentence
  (`RAISE EXCEPTION 'Only the host …'`) is the message; Postgres wording, SQLSTATEs and class
  names become a plain sentence ("You don't have access to do that in this meeting.", "This
  meeting changed a moment ago…", "The server could not be reached."); the raw error stays on
  `cause`. `SERVER_FAILED_SENTENCE` / `isRawServerText` now live in `core/errors.ts`
  (still re-exported from the root).
- **Consumer action:** none.

## 0.7.8
### (next patch)

- **Fix — a refusal never reaches a person as a Python class name** (verifier, 2026-09-27): a
  host's toast read "Internal server error: UnsupportedClientFeatureError". `readServerRefusal`
  now skips raw server text (`Internal server error: …`, a bare `…Error` class, a snake_case
  code) and falls through to the body's `user_message`, or to `SERVER_FAILED_SENTENCE`. The
  three hand-copied refusal builders (`in-room.ts`, `management.ts`, `after.ts`) collapse into
  ONE exported `refusalFromResponse(response, operation, fallbackRemedy)`, whose `message` is
  the server's sentence — no `meet.x:` operation prefix — and whose `remedy` is the server's.
- **Fix — a refused Remove / Make host keeps its pane open WITH the sentence** : the confirm
  pane now shows the server's refusal inline (`role="alert"`) with its buttons live; before it
  stayed up silently.
- **Fix — every per-person menu offers "Message privately"**: a tile's menu (Room stage and a
  host layout's strip, e.g. the Board) now opens the private chat through the store
  (`snapshot.privateChatRequest`, answered by the one `ControlBar`); before only the People
  panel could, so two menus for one person disagreed.
- **Fix — attendance is per PERSON, never per join**: an export listed the same attendee twice
  under "attended". `attendanceReport` merges a person's joins (account = one person across
  devices and rejoins; an accountless guest's rejoin, which gets a fresh `guest:<random>`
  identity, is matched by the typed name) into one row — first arrival, last departure, total
  time with overlaps counted once, and a new `joins` count. `notesDocument`'s "Attended" line
  and the record view's "Who attended" use the new exported `uniqueAttendees` / `attendeeKey`.
- **Consumer action:** none (a consumer counting `bundle.attendees` itself should count
  `uniqueAttendees(...)` instead).

## 0.7.7
### Meet wave 3: after the meeting

- **Fix — the room header on a phone** (2026-09-27, 390px): the header wraps into rows and
  each host control stays one line; before, the title read "Hollo…" and "End meeting for
  everyone" stacked into four underlined words.
- **Fix — a removed participant reads the server's sentence, not "invalid token: revoked"**
  (2026-09-27, live room): a server-side disconnect now invalidates the cached room token, so
  the next Join mints again and shows "A host removed you from this meeting."
- **Fix — a host's per-person menu opens beside its button** (2026-09-27, live room): the
  popover was hidden but IN FLOW on its first frame, so the anchor it measured was button +
  menu and it opened a full menu-height lower — Make host, Report and Remove from meeting were
  below the fold. It is now fixed-positioned from the first frame, re-placed when the layout
  moves, and pinned + scrollable when neither side of the button holds it.
- **Fix — the waiting room for people from another organization and for denied guests**
  (2026-09-27, verified in a real room): a signed-in link-follower now knocks as `user:<id>`, the
  identity the server's lobby row carries — before, the host saw two entries and admitting one
  left the person waiting forever. A host's "Deny" now reaches the knocker (`onDenied` →
  `engine.rejoin()`), and every token refusal (403/423/410) shows the SERVER's own sentence and
  remedy instead of a developer note ("meet.token: the server refused this join (403)").
- **`core/after.ts`** (framework-free, exported from the root): `attendanceReport` /
  `attendanceTotals` / `attendanceCsv` (who joined, when, how long — measured to the meeting's end
  when a leave was never recorded — and as what: host, co-host, member, signed-in guest, guest,
  assistant); `transcriptDocument(bundle, "txt" | "md" | "vtt")` (VTT cued from the recording's
  start); `notesDocument` (summary, decisions, action items with owners, as Markdown);
  `recordingOffsetMs` (a transcript moment as a position in the recording — `null` outside it,
  never clamped) + `clockPosition`; `searchTranscript` (in-record search with match ranges);
  `loadMeetingChat` (the saved chat of a finished meeting, a refusal returned as data);
  `loadFollowUpDraft` / `sendFollowUp` (the recap email — `MEET_ROUTES.meetingFollowUp`,
  `GET|POST /api/v1/meet/meetings/{id}/follow-up`; `dryRun` renders and queues nothing).
- **Repository:** `myRecordings({ includeArchived })` (`meet_my_recordings`), `searchMeetings(q)`
  (`meet_search` — titles, agendas, current notes, final transcript lines; snippets mark hits
  with ⟦ ⟧, split with `snippetRuns`), `renameRecording`, `setRecordingArchived` (soft delete;
  an update RLS refuses is an error, never a silent success). `recordings(meetingId)` now leaves
  archived recordings out of the record.
- **Consumer action:** none required. A test double that implements `MeetRepository` must add
  the four new methods (`managementStubs()` refuses them by name); a fake PostgREST chain used
  under `recordings()` must answer `.is()`.

## 0.7.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.5..npm/meet/v0.7.6 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.5
### Unreleased

### Meet wave 2 — in-meeting parity with Zoom and Google Meet

- **Host moderation now actually happens — through the server.** Through 0.7.x "Mute" and
  "Remove" were published on the room's data plane (`mx.host`) and nothing in the platform
  listened: the buttons did nothing. Every host power is now `POST /api/v1/meet/moderate`
  (`MEET_ROUTES.moderate`), decided by aidream from the meeting itself and performed by the SFU:
  mute, stop video, stop screen share, mute everyone, ask to unmute (one / everyone), spotlight
  for everyone, lower a hand / all hands, make or remove a co-host, make someone the host
  (host only; the previous host stays a co-host), rename (self, or a host renaming others),
  remove (cannot rejoin), report. New `core/in-room.ts` (`createInRoomClient`,
  `ModerationAction`, `MODERATION_ACTIONS`), exposed as `MeetHost.inRoom`.
  `useHostControls()` gains `run(request)`, `isOwner`, `busy`, and a shared `notice` (the
  server's sentence, kept in the snapshot as `actionNotice`).
- **In-meeting chat is SAVED.** A message to everyone goes through `POST /api/v1/meet/chat`,
  is stored in `communication.meet_chat_messages`, and reaches the room as the server's
  `matrx.meet.chat`; every join reads the history (`GET ?meeting_id=`), so a late joiner sees
  what was said. It shows at once as pending, then saved; a refusal stays visible with the
  server's reason. **Private messages** to one person: `engine.sendPrivateChat(to, text)` /
  `useRoom().sendPrivateChat` — data plane only, addressed to that identity, never saved.
- **Spotlight for everyone** rides the room's metadata (server-set, delivered on join);
  `snapshot.spotlight`, `computeStage({ spotlight })` — a local pin outranks it, a screen share
  outranks both.
- **Raise hand queue** — hands carry when they went up (`MeetParticipant.handRaisedAt`),
  `useHandQueue()` orders them, a raised hand is re-announced (with its original time) to a late
  joiner, and a host lowers one or all.
- **Reactions** — a reaction picker in the control bar (Meet's nine), and the overlay is finally
  mounted (`<MeetingOverlays>` inside `<ControlBar>`).
- **What a host did to me** — `matrx.meet.moderation` packets become `snapshot.moderationNotice`
  ("Ana is asking you to unmute" with an Unmute button; muted; removed). A demotion re-grants
  the local role, so an old host loses host controls at once.
- **Noise suppression and background blur / picture** — `engine.setEffects()` /
  `useMediaEffects()`, applied live and remembered per browser (`createWebEffectsStorage`);
  background effects via `@livekit/track-processors` (new dependency, imported lazily), absent
  on a browser without WebGL2.
- **Devices** — the chosen microphone / camera / speaker are now APPLIED on join (the pickers
  used to store a choice nothing read); `<SettingsPanel>` switches them live with a microphone
  level meter and a speaker test; the pre-join screen has a live camera preview (with the chosen
  background), a mic level meter, a speaker picker + test, and the effects.
- **Connection quality** — `<QualityBars>` on every tile and People row.
- **People panel** — search, the hand queue with Lower all, Mute all / Ask all to unmute, and a
  per-person menu (`<PersonMenu>`, also on every tile) with every action the actor may take.
- **Keyboard shortcuts** (Meet's chords): ⌘/Ctrl+D mic, ⌘/Ctrl+E camera, ⌘/Ctrl+Alt+H hand,
  ⌘/Ctrl+Alt+C chat, ⌘/Ctrl+Alt+P people, Shift+? the sheet (`useMeetShortcuts`,
  `MEET_SHORTCUTS`, `<ShortcutsSheet>`).
- **Exported for host layouts**: `HostMenu` (lock / end — the Board layout can now render it),
  `MeetingOverlays`, `PersonMenu`, `SettingsPanel`, `ShortcutsSheet`, `QualityBars`,
  `MEET_REACTIONS`, new icons.

### Removed

- `RoomEngine.publishHostAction`, `HostAction`, `DATA_TOPICS.hostAction` — a control plane
  nobody listened to (see above). `RoomEngine.sendChat` → `MeetHost.inRoom.sendChat` (saved).

### Consumer action

matrx-frontend: bump `@ai-matrx/meet`; the Board header renders `<HostMenu>` beside its host
controls. Everything else arrives through `<ControlBar>`, which both layouts already render.
Requires the aidream routes `/v1/meet/moderate` and `/v1/meet/chat`.

## 0.7.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.3..npm/meet/v0.7.4 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.2..npm/meet/v0.7.3 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.2
### Unreleased

- **Meet wave 1 — meetings you can manage day to day.** New `MeetRepository` methods over the
  new database doors: `scheduleMeeting`, `updateMeeting` (JSON patch, version compare-and-swap),
  `cancelMeeting`, `archiveMeeting`, `restoreMeeting`, `setOccurrence` (cancel / move / restore one
  occurrence of a series), `meetingOccurrences`, `occurrencesBetween` (the Upcoming list),
  `invitees`, `addInvitees`, `setInviteeRole`, `removeInvitee`, `respond`, and the account-less
  `invitationByToken` / `respondByToken`. New server calls `announceMeeting` (send invitations /
  updates / cancellations with `.ics`, re-queue reminders — call after every save) and
  `pushMeetingToGoogleCalendar` (gated by the server). `MeetingRecord` gains `timeZone`, `agenda`,
  `recurrenceRule`, `joinBeforeHost`, `cancelledAt`, `cancellationReason`, `deletedAt`,
  `calendarSequence`, `version` (optional in the type). New `MeetErrorCode` `cancelled` (a join to
  a cancelled meeting, 410, with the server's sentence); `40001` / `55000` now map to `conflict`.
  `MEET_ROUTES.meetingAnnounce` / `meetingGoogleCalendar` + `meetingPath()`. Consumer action: none
  required; the management screens (matrx-frontend, wave 1 UI) adopt the new methods.

- **A meeting link works for people from any organization.** The server now admits a
  signed-in person from another organization who holds the link (guest rules: lobby,
  host admission). The durable feed's meeting re-read fell over for them, because RLS
  does not show them the row; it now falls back to the link door
  (`meet_meeting_by_slug`) a guest already uses. No consumer action required.

## 0.7.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.7.0..npm/meet/v0.7.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.7.0 — 2026-09-25

### Added

- **`<MeetingRoom headerControls={…}>` — a host-provided control in the live room's
  header.** The room is this package's stage, so a host had nowhere to put its own
  meeting-level affordance: matrx-frontend's Intelligence icon for the meeting's AI jobs
  sat only on its `/meetings` list header, away from the meeting it describes. The node
  renders in the in-meeting header row, before the host menu, inside
  `.mx-meet__header-controls` (never squeezed); pre-join, lobby and the ended-meeting
  record carry none. Guard: `<MeetingRoom headerControls>` in `components.test.tsx`
  (red on 0.6.5).

### Consumer action (C28)

None required — the prop is optional. A host that wants its control in the room passes it.

## 0.6.5 — 2026-09-24

### Fixed

- **`<MeetProvider>` no longer shadows the host's realtime manager.** Found live in
  matrx-frontend (admin@admin.com, no organization selected): `/messages` sat on its
  skeleton forever and a 64-message group showed "Conversation 0 members". The app mounts
  ONE `<RealtimeProvider>`, then `<MeetProvider>`, then `<MessagingProvider>`. Through 0.6.4
  MeetProvider always mounted a `<RealtimeProvider>` of its own with `client={null}` until an
  organization was selected, so everything under it read a NULL manager while a provider was
  "mounted" above it — messaging (which correctly rides a mounted host provider) waited
  forever. With an organization selected it was a SECOND manager: two sockets and two write
  ledgers, the class `@ai-matrx/realtime` 0.3.0's `useIsRealtimeProviderMounted()` exists to
  close. MeetProvider now uses that probe and rides the host's provider when one is mounted;
  when none is, the provider it mounts connects as the person and never waits on an
  organization (access belongs to the person, 2026-09-23). What Meet's own runtime needs an
  organization for is unchanged. Guard: `src/react/host-realtime.test.tsx` (red on 0.6.4).

### Consumer action (C28)

None required — adopt the new version. A host that mounts its own `<RealtimeProvider>` above
`<MeetProvider>` (matrx-frontend's `RealtimeHost`) now has exactly one manager, as its comments
already claimed; realtime consumers under Meet stop seeing `null` with no org selected.

## 0.6.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.6.3..npm/meet/v0.6.4 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.6.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.6.2..npm/meet/v0.6.3 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.6.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.6.1..npm/meet/v0.6.2 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.6.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.6.0..npm/meet/v0.6.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.6.0 — 2026-09-14

### Changed — BREAKING

- **`<MeetProvider userId>` is gone. Meet reads the acting user from the
  Supabase client's own session** (DD-240). The prop took the user id from a
  copy the host kept in its own state; nothing rewrote that copy when the
  browser's domain-wide auth cookie rotated to another account, so the package
  went on issuing `communication.meet_pending_call_invites(p_user_id => …)` for
  an identity the tab could no longer prove it was. The database refused those
  reads exactly as it should — `42501` at HTTP 403, *"meet: the acting user
  (4cf62e4e…) is not the authenticated user"* — in tabs 8.8 h and 32.9 h old on
  production, 2026-09-14. There is now ONE source for who is acting, and it is
  the same client that mints the JWT on every request, so the two cannot
  disagree.

### Consumer action

- **Delete the `userId` prop from your `<MeetProvider>`.** Nothing replaces it.
  `displayName` and `avatarUrl` stay — they are chrome, not identity. A
  `userId` still passed at run time is ignored and reported through
  `onDiagnostic` at `error` level with the remedy, rather than dropped
  silently.
- **A guest join is unchanged**: `guestName` is now the ONE way to declare the
  guest lane, and that mount reads no session at all (D6).
- **Expect one honest inert tick.** The provider is inert until the first
  `getSession()` answers — a runtime built before then would issue its first
  user-scoped read on behalf of a guess. Components already handle it
  (`<MeetingRoom>`, `<PreJoin>`, `<ScheduleMeeting>` render their honest
  sentence; ambient components render nothing), and tests that used to assert
  synchronously after `render()` now await.

### Fixed

- `<RecordingPlayer>` is a PUBLIC entry component that called
  `useRequiredMeetHost()` and therefore THREW a package error into a consumer's
  tree whenever the provider was inert (rule 7c). It renders the honest inert
  notice instead.

## 0.5.16

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.5.15..npm/meet/v0.5.16 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.5.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.5.14..npm/meet/v0.5.15 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.5.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.5.13..npm/meet/v0.5.14 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.5.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.5.12..npm/meet/v0.5.13 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.5.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.5.11..npm/meet/v0.5.12 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.5.11 — 2026-09-12

### Fixed

- A repaired wrap-up now projects its active summary in the live assistant,
  member record, and guest record. The superseded summary remains durable
  history and can no longer reappear as the current record.

## 0.5.10 — 2026-09-12

### Fixed

- Note-taker status text now separates a connected assistant listening for
  speech from durable transcription output. Connected-but-unconfirmed and
  interrupted/reconnecting states remain explicit without inferring a cause,
  duration, or completed capture.

## 0.5.9 — 2026-09-12

### Fixed

- Note-taker state now requires both the durable `transcribing` state and the
  exact meeting note-taker identity before it says capture is active. Generic
  agent presence, joining, handoff, and stopped states keep their own neutral,
  durable meaning.
- A fully refused guest record presents one grouped access explanation instead
  of repeating that refusal in every empty section. Partial records retain each
  accessible or differently refused section.
- Recording playback reports only observed media failure and recovery. A
  byte-backed decode failure now replaces the broken player with a retry path.

## 0.5.8 — 2026-09-12

### Fixed

- A durable assistant attendance row now says `Meeting assistant`; it no longer
  claims the assistant transcribed the meeting without transcript evidence.
  Empty notes and transcript panels say only what their empty durable reads
  establish, which covers recording-only transcription correctly.
- A guest looking at a finished record sees the real Q&A boundary and no input
  the server will refuse. Signed-in members retain post-meeting Q&A, while
  guests can continue to ask during a live meeting.
- Recording fallback copy no longer asserts playback quality before the media
  element has loaded. It states that the file is downloading and when seeking
  becomes available.

## 0.5.7 — 2026-09-12

### Fixed

- The permanently dark meeting stage now owns its paired foreground, muted
  metadata, and video-tile label tokens. A host light theme can no longer turn
  a speaker label or header metadata low-contrast by remapping page tokens.
- A fully restricted guest record marks attendance, notes, transcript, and
  recording as refused. Its attendance section therefore never mistakes a
  denied roster for proof that nobody attended.

## 0.5.6 — 2026-09-12

### Fixed

- The meeting record header now aligns with the readable record column. A
  recording is a compact card with a normal-width Play control; internal file
  transport details no longer compete with the recording itself.
- Caption trust documentation now names
  `lk.transcribed_participant_identity` as the speaker carrier and preserves
  the SFU-verified note-taker publisher requirement.

## 0.5.5 — 2026-09-12

### Fixed

- A partially shared meeting record now presents one calm, specific access state
  for restrictions with the same remedy instead of stacking one error card for
  every unavailable part. Different failures remain separate, and each record
  section still states whether its own absence is a policy boundary or real
  absence.
- When an existing guest record view receives a member identity after session
  hydration, it automatically re-reads through member RLS without requiring a
  page refresh. The stale guest access state is hidden while that current read
  resolves.

## 0.5.4 — 2026-09-12

### Fixed

- Guest Q&A now carries the current meeting's admitted room pass in the existing
  `x-meet-room-token` header. The provider uses the active room token only when
  it belongs to the loaded meeting, otherwise its remembered guest pass for that
  same room. The Q&A body remains exactly `{meeting_id, question}`: no
  transcript, roster, or credential enters it. A missing pass leaves the header
  absent so aidream can refuse with its own explanation; signed-in record Q&A
  continues to use the session without a room-pass header.

## 0.5.3 — 2026-09-12

### Fixed

- Central recording retention is now an honest record state. A warning receipt keeps an
  available recording playable and shows its due date beside the player. An expired recording
  has no player or file id and says plainly that the transcript and notes remain available.
- `expired` is accepted consistently from durable recording rows and the minimal live room
  packet, rather than falling through to `failed` or leaving a room indicator stale.

## 0.5.2 — 2026-09-12

### Fixed

- A room participant could publish a `matrx.meet.*` topic and the client treated it as an
  aidream event. The room engine now accepts that namespace only when LiveKit reports no
  participant sender, the carrier used by `RoomService.SendData`; every participant packet,
  including one with an agent-looking identity, is visibly rejected.
- A participant could publish a caption stream claiming any `sender_identity`. Captions now
  require the SFU-reported publisher to be the loaded meeting's exact deterministic note-taker;
  the first real caption remains accepted before a roster event. Captions remain display hints
  and durable transcript rows remain authoritative.
- A `meet_notes` realtime UPDATE that only set `metadata.superseded_by` was treated as unchanged
  by the store, leaving an obsolete decision or action item in current lists. The metadata marker
  now participates in note replacement; timeline provenance remains intact.

## 0.5.1 — 2026-09-12

### Consumer action (C28)

- Upgrade. No API change. `meetingWhen` now says "· 19 minutes" via
  `formatDurationMs`, so three lengths read differently: a sub-minute meeting
  says "40 seconds" where it said "0 minutes", a 19m35s meeting says
  "19 minutes" where it said "20 minutes", and a 90-minute meeting says
  "1 hour" where it said "90 minutes".

### Fixed

- `meetingWhen` no longer carries its own duration formatter. It divided by
  `60_000`, rounded, and pluralised "minute" itself — a package duplicating
  another package's capability, found the day `aidream/apps/shared` became a
  scan root for `check:package-twins`. It now calls `formatDurationMs(ms,
  { style: "long" })` from `@ai-matrx/kit/format`, which this package already
  depended on. The worst of the three old readings was the confident zero: a
  forty-second meeting that demonstrably happened announced "0 minutes".


## 0.5.0 — 2026-09-12

**A guest who attended a meeting can read what it produced.** (MRF-1.)

### Fixed

- 🚨 **THE PERSON WHO WAS IN THE MEETING COULD NOT READ ITS SUMMARY.** A guest
  following a meeting link afterwards got the title, the time, and four honest
  refusal sentences where the record should have been. Every one of those
  sentences was correct — `anon` holds no grant on `meet_notes`,
  `meet_transcript_segments`, `meet_participants` or `meet_recordings`, and
  widening that is forbidden (MRI-A10: a meeting link is not organization
  membership) — and the product was still wrong. Somebody sat through a meeting,
  spoke, was transcribed, and was then told there was nothing to see.

  Arman ruled on 2026-09-12 that guest access to the record is a
  **configuration and never a question**: a system default, an organization
  override, and a per-meeting setting a host chooses. So the record now has two
  doors to one screen. A signed-in member reads it direct from Supabase under
  RLS exactly as before. A guest reads what the organization shares through
  `GET /api/v1/meet/record`, **authorized by the room token this device already
  holds** — no widened RLS, no signed URL, no anonymous grant. Both produce the
  same `MeetingRecordBundle`, so the two readers can never drift into two
  products.

### Added

- **`loadGuestMeetingRecord`** — the guest lane's read. `useMeetingRecord`
  chooses it automatically for a reader with no session, so a consumer changes
  nothing. What the level does **not** share is stated as a
  `MeetingRecordRestriction` built from the server's own `shared` map, never
  inferred from an empty array: "not shared with guests" and "nothing was said
  in this meeting" stay different sentences (the MRI-A16 class).
- **The pass survives the tab.** `createTokenClient` now takes an optional
  `RoomTokenStorage` (`createWebRoomTokenStorage`, wired by `<MeetProvider>`)
  and keeps a **guest's** room token — never a member's, who can always mint
  another from their session. This is what makes the durable link durable for a
  guest: `/token` refuses to mint for a finished meeting, correctly, so without
  a kept pass a guest has no way to prove they were ever there. `invalidate()`
  deliberately does NOT forget it — leaving a meeting is exactly when a guest
  starts wanting its summary. An expired pass produces the server's renew
  sentence ("open the meeting link again"), never "you were never here"; how
  long a pass lasts is the organization's `meet.room_token_ttl_seconds`.
- **`<GuestRecordAccessControl>`** — the host's control, mounted in the record
  view and in the People panel (so a host can decide while the meeting is
  running, which is when they know who is in the room). Host-only and **absent**
  for everyone else, never greyed. "Use my organization's setting" is rendered
  with what that resolves to today, because a label that does not say what the
  organization chose makes the host guess about other people's words.
- **`useGuestRecordAccess`**, **`setGuestRecordAccess`**,
  **`guestRecordAccessSetting/Label/Explanation`**,
  `GUEST_RECORD_ACCESS_LEVELS` / `_CHOICES` / `_METADATA_KEY`, and the
  `GuestRecordAccess` / `GuestRecordAccessSetting` / `GuestRecordAccessResolution`
  types. `MEET_ROUTES` gains `record` and `recordAccess`; `ROOM_TOKEN_HEADER`
  pins the header the pass rides on — a header and not a query parameter,
  because a bearer credential in a URL lands in access logs, referrers and
  browser history.

### Note for consumers

Nothing to do. `useMeetingRecord` and `<MeetingRecordView>` pick the right door
by themselves. A consumer with its own meeting-settings screen can render the
exported `<GuestRecordAccessControl>` rather than building a second one.

## 0.4.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.4.14..npm/meet/v0.4.15 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.4.14 — 2026-09-09

Meeting-end notices now trigger a bounded canonical metadata verification before
the room engine disconnects gracefully. A packet alone cannot end a meeting;
failed verification leaves the transport intact, and stale reads cannot tear
down a replacement session. The provider supplies the existing public metadata
RPC. SDK errors remain visible. Real browser shutdown acceptance is still required.

## 0.4.13 — 2026-09-09

The last three findings of the fifth independent verifier's production run, and
the first ruling to come out of it. (MRI-A18; D20.)

### Fixed

- 🚨 **A RAW LIVEKIT IDENTITY, ON FOUR SURFACES, IN BOTH LANES.** 0.4.12 shipped
  `core/names.ts` and the caption rail and transcript stopped printing tokens —
  and the ROSTER never asked it. `core/room.ts:186` read
  `driver.name.length > 0 ? driver.name : driver.identity`, inside the one
  structure the name book is TAUGHT from, so `guest:v-spk-1` was rendered in the
  participant strip, the People panel, the meeting header and every Pin/Mute
  `aria-label` — in the HOST lane as well as the guest lane. Every roster path
  now resolves through the name book: a participant whose token carried no name
  is **"Unnamed guest"**, an agent is **its product name** ("Matrx note-taker"),
  and a name a durable row or a caption taught later wins the roster back.
  The LiveKit driver no longer invents a name either — it reports what LiveKit
  said, empty string and all, and the ONE fallback lives in `projectParticipant`.
  `src/core/roster-names.test.ts` drives a roster event with an empty driver name
  and then **scans the whole store snapshot** for any rendered value that is a
  raw identity, so a future surface cannot reintroduce this without failing.

- 🚨 **"NO WRAP-UP WAS WRITTEN FOR THIS MEETING" — FOR THE 33 SECONDS IT WAS
  BEING WRITTEN.** On production 2026-09-09 the HOST's record view asserted that
  absence between the flip to the record (04:46:42) and the wrap-up landing
  (04:47:15). The read was permitted and genuinely empty, so this is not the
  refused-read class 0.4.8 closed; it is the other way a screen lies, by reading
  "not here yet" as "never happened". `wrapUpStatus()` now answers with one of
  five states from the meeting row's own job marker — `written`, `in-progress`
  (with a **Check again** affordance, and no timeout that turns it into a false
  absence), `failed` **in the server's own words**, `refused`, or the one
  `absent` that is EARNED: a meeting no note-taker ever attended.

- 🚨 **THE SAME DECISION, LISTED TWICE (D20).** A live-notes window and the
  wrap-up both write decisions and action items over the same conversation, so
  meeting `9cb-39f8-782` held 2 decision rows with ONE distinct text and 4
  action-item rows with THREE — and the record view rendered all of them. The
  wrap-up is now the **authoritative post-meeting set**: the server marks the
  window rows it restates (`metadata.superseded_by`) and never deletes them, and
  the record view and the assistant panel list only unsuperseded decisions and
  action items. The window notes remain the timeline.

### Added

- `wrapUpStatus(bundle)` and the `WrapUpStatus` type.
- `MeetingNote.supersededBy` — D20's marker, projected from
  `metadata.superseded_by`, which rides the `matrx.meet.note` room packet too so
  a session-less guest is not the one lane still reading a decision twice.
- `isAgentIdentity`, `isPlaceholderName`, `placeholderNameFor` and the named
  placeholder constants in `core/names.ts`.

### Changed — consumer-visible

- `snapshotParticipant()` returns `name: ""` for a participant LiveKit gave no
  name, where it used to return the identity. `projectParticipant()` turns that
  into the honest placeholder. A consumer that read `DriverParticipant.name`
  expecting a never-empty string should read `MeetParticipant.displayName`.

**Consumer action: none** beyond taking `latest`.

## 0.4.12 — 2026-09-09

The third defect found by attacking 0.4.8's own change. (MRI-A16.)

### Fixed

- 🚨 **A `<video>` THAT SILENTLY NEVER PLAYED.** 0.4.8 made a recording stream
  on the platform's `mx_files_session` cookie instead of downloading tens of
  megabytes first — and a `SameSite=None` cookie is exactly what a browser with
  strict third-party-cookie settings refuses, with no status this code can read.
  The result was a video element that simply sat there: no error, no sentence,
  nothing to do. A refused stream is now RE-READ through the bytes lane (which
  carries the organization header and needs no cookie) **and the screen says
  that is what happened**, rather than leaving a person looking at a player
  that will never start.

### Added

- `<RecordingPlayer fileId>` is exported. The record view uses it, and a
  consumer with a platform recording elsewhere should not have to reimplement
  the file lane — which is how the 400 this whole item is about got written.

**Consumer action: none** beyond taking `latest`.

## 0.4.11 — 2026-09-09

The second defect found by attacking 0.4.8's own change rather than waiting for
somebody to hit it. (MRI-A16.)

### Fixed

- 🚨 **A GHOST KNOCKER THE HOST COULD NEVER DISMISS.** 0.4.8 made the host's
  People panel read the durable `meet_participants` rows so a knocker could not
  hide behind a broadcast this browser never heard — and then IGNORED any row
  that was not `knocking`. Admitting a guest from another tab writes `admitted`
  on that same feed, so this host kept offering to admit somebody who was
  already in the meeting, forever, with no way to clear them. The durable row is
  the verdict wherever it was made: a decided row now moves the person out of
  the waiting list, on the live feed and on the backfill door alike.

**Consumer action: none** beyond taking `latest`.

## 0.4.10 — 2026-09-09

- Guest ended-record views now return explicit sign-in restrictions without
  issuing four private-table requests that always fail for anonymous readers.
  Public meeting metadata remains visible; authenticated partial record reads
  remain protected by RLS. Identity changes reload the record and hide the
  previous reader's bundle immediately.

## 0.4.9 — 2026-09-09

The defect 0.4.8 walked into by fixing the one above it, found by attacking my
own change rather than by waiting for somebody to hit it.

### Fixed

- 🚨 **A HOST COULD ONLY EVER RECORD ONCE PER SESSION.** The recording control
  asked `state === "idle"`, which was survivable only while the store never
  learned any state but the one this browser patched. The moment 0.4.8 made the
  DURABLE states arrive, a meeting whose recording had finished sat on
  `available` forever and **the Start control silently stopped existing** — a
  control absent for something the host may plainly do, which is the same class
  of lie as a dead button. `isRecordingInFlight()` is now the one definition of
  "a recording is running" (`starting` / `recording` / `stopping`, and nothing
  else), asked by the control and by the store's precedence rule alike.

- **The store's "the database wins" rule is about ONE RECORDING, not about the
  meeting.** `setRecording` now carries the recording's id, so a LATE
  `matrx.meet.recording` packet about the recording that just finished is told
  apart from the FIRST packet of the next one. Without that, D16 either pinned
  the indicator on `available` forever or let a stale packet drag a finished
  recording back to `recording`; both were reachable.

**Consumer action: none** beyond taking `latest`.

## 0.4.8 — 2026-09-09

The six defects MRI-C8's independent production verification found, each fixed
as a class with a test proven RED first. (0.4.7 was taken in parallel by the
guest-transport fix; this is on top of it.)

### Fixed

- 🚨 **A RECORDING THAT LANDED PERFECTLY COULD NOT BE PLAYED.** The record view
  fetched `GET /files/{id}/download?inline=1` through a bare `fetch` carrying
  only a bearer token, so aidream's org-admission middleware answered 400
  `organization_required` — the identical URL returned `200 video/mp4` with
  `X-Organization-Id` added by hand. 0.1.3 had already learned this and put the
  header on three callers BY HAND, which fixed three instances and left the
  class open; a fourth caller was written months later and never heard the
  lesson. **`core/api.ts` is now the ONE place a request to aidream is built**,
  and `core/api.test.ts` fails the build if a second way appears — proven RED on
  all three legs (a bare `fetch` to a base url, an element `src=` at one, a
  hand-written organization header). Playback also stops downloading the whole
  file when it does not have to: the platform's `POST /files/session` cookie
  lets `<video>` stream, and when that mint is refused the bytes are read with
  the header instead **and the screen says so**.

- 🚨 **THE RECORDING INDICATOR NEVER WENT SOLID.** `meet_recordings` moved
  `starting` → `recording` → `available` one second after each trigger, and
  nobody was told: the only thing that ever reached the store was this browser's
  own optimistic patch, so the host read "Starting recording…" for a whole
  meeting and "Stopping recording…" afterwards. Both transports are wired now —
  the durable row over Postgres Changes for a member (re-read on backfill, so a
  socket blip cannot leave the indicator lying), and `matrx.meet.recording` in
  the room for a guest. The two cannot undo each other: the database wins (D16).

- 🚨 **A GUEST SAW NO RECORDING INDICATOR AT ALL**, although PreJoin promises
  every joiner one. `anon` holds no grant on `meet_recordings` and MRI-A10
  settled that widening it is forbidden, so the server now publishes
  `matrx.meet.recording` at every state change and the package consumes it in
  both lanes. **Requires the matching aidream change** (`recording.py`,
  `webhooks.py`, `recording_landing.py`); without it a guest sees the same
  nothing as before, never a wrong indicator.

- 🚨 **A RAW LIVEKIT IDENTITY WAS RENDERED AS A PERSON'S NAME.** The guest's
  caption rail read `guest:c8-spk-1` in the same second the host's read "Ada
  Lovelace". Nothing resolves a name for itself any more: `core/names.ts` is one
  book that every source teaches and every surface asks. It never unlearns (so
  somebody who left keeps their name), it refuses to learn an identity as its
  own name (which is how the token got on screen), and when nothing anywhere
  knows one it says "Unnamed guest" rather than a token. `groupTranscript` now
  breaks on IDENTITY, so two unnamed people can never merge into one speaker.

- **A knocking participant was invisible unless that browser held the lobby
  channel.** A host who joined second or reloaded left a guest waiting forever.
  The host lane subscribes to the durable `meet_participants` rows and re-reads
  them on backfill; a guest opens no such feed.

- **The guest record view claimed "No wrap-up was written" directly under a
  panel that had just refused to read those rows.** `absenceSentence()` is now
  the one place an empty slot gets its words, and over a refused read it says
  *"This record is not shared with guests."* Attendance no longer vanishes
  silently when its read is refused either.

**Consumer action: none** beyond taking `latest`. `TranscriptLine` and
`TranscriptBlock` each gained an `identity` field; nothing was removed.

## 0.4.7 — 2026-09-09

### Fixed

- Guest meeting sessions no longer open private Postgres transcript/notes feeds.
  Guest room metadata reconnects through the public slug RPC; live room events
  remain active. Saved history requires a signed-in session with access. Member
  durable backfills retain all three reads.

**Consumer action:** update to latest; no database grant changes.

## 0.4.6 — 2026-09-09

### Fixed

- 🚨 **A GUEST WAS BADGED "Co-host" ON THEIR OWN TILE.** `<ParticipantTile>`
  read `role !== "participant" ? "Host" : "Co-host"` — true while there were
  only three roles, and a LIE the moment 0.4.0 added a fourth. Observed live on
  a real guest the instant the host admitted them. There is now ONE `<RoleBadge>`
  serving every surface, so a new role can never again be rendered as the wrong
  one by a surface that did not hear about it. Four cases guarded (host,
  co-host, guest, and a participant badged NOTHING), proven RED.

**Consumer action: none** beyond taking `latest`.

## 0.4.5 — 2026-09-08

### Fixed

- **The People panel badged the LOCAL row from the roster alone**, so a host
  whose controls rendered from the token grant could see their own row unbadged
  — the two carriers of one decision disagreeing on one screen (MRI-A11). The
  local row now reads `useRole()`, the same effective role every host control
  asks. Guarded, proven RED; a second case pins that an ordinary participant is
  badged NOTHING rather than "Participant".

**Consumer action: none** beyond taking `latest`.

## 0.4.4 — 2026-09-08

**Admitting a guest from the lobby has never worked, in any version.** Found by
MRI-A11's browser proof, one layer below the 0.4.2 fix. (0.4.3 is MRI-A12's
release, published in parallel from the same tree — this is on top of it.)

### Fixed

- 🚨 **A `RETURNS void` RPC RESOLVES TO `null`, AND THAT IS SUCCESS.** Both
  `communication.meet_admit_participant` and `meet_record_consent` are `RETURNS
  void` on the live database (verified with `pg_get_function_result`), and both
  were routed through the row-EXPECTING guard — so each performed its write and
  then threw *"the database returned no row"* on the way back. The row was
  already written; the client believed the call had failed, so the broadcast
  that tells the knocker never went out, `store.upsertLobby` never ran, and the
  host's panel kept showing a guest who had in fact been admitted while the
  guest sat in the waiting room forever. D10's *"consent is RECORDED, not
  assumed"* was failing the same way, silently, on every acknowledgement.
  `guardVoid` is the void path; `VOID_RPCS` pins which RPCs take it, verified
  against the live return types, so a future void RPC cannot be wired to the
  wrong guard by accident.

### Guards

Four cases against a client that answers `{data: null, error: null}` — exactly
what PostgREST returns for a void function: admit resolves, consent resolves,
the declared void list matches, and an RPC that DOES return a row still refuses
a null body loudly. The first two proven RED against 0.4.2.

**Consumer action: none** beyond taking `latest`.

## 0.4.3 — 2026-09-08

**A guest had no meeting record, so the meeting's intelligence was invisible to
them — and 0.4.1's room events could not reach them either.** Register MRI-A12,
found in a real guest browser on production, which is the only place it was ever
visible. (0.4.2 is MRI-A11's repair, published in parallel from the same tree;
this is on top of it.)

### Fixed — `snapshot.meeting` was null forever for a session-less guest

`snapshot.meeting` was written by exactly one thing: the Postgres Changes
subscription and its backfill, both of which read the `meet_meetings` TABLE.
`anon` cannot select that table — only the `meet_meeting_by_slug` RPC is granted
to it — so for a guest the field stayed `null` for the entire meeting, and
everything keyed on it silently did not exist:

- **no Meeting-assistant control at all** (`AiControl` returns `null` when there
  is no meeting), so no notes, no decisions, no action items, no Q&A on screen;
- **no live captions** — `room.ts` drops a caption it has no meeting to
  reconcile against (D16);
- **no `matrx.meet.note_taker` and no `matrx.meet.ended`** — 0.4.1's own room
  events, which must be matched to this meeting before they are applied.

The room worked, the guest saw tiles and a roster, and none of the meeting's
intelligence was reachable. Nothing said so.

`<MeetingRoom meeting={…}>` already takes the row the consumer resolved through
the one door `anon` HAS — `meet_meeting_by_slug` — and used it only to decide
whether the meeting had ended. It now seeds `snapshot.meeting` when the store has
none, which is what makes "the resolved row travels with the room" true rather
than aspirational. Written ONCE, only into an empty store: the durable feed's
copy is the record and is never overwritten by the prop it started from (D16).

### Consumer action

**None**, if you already pass `meeting` to `<MeetingRoom>` (matrx-frontend does,
on both lanes). If you do not, a guest lane on your surface has been silently
missing every durable thing the meeting produced — pass the row you resolved
from `meet_meeting_by_slug`.

## 0.4.2 — 2026-09-08

**Two defects the browser proof of 0.4.0 exposed within the hour, both in the
lane 0.4.0 opened.** Register MRI-A11. (0.4.1 is MRI-A12's release, published
in parallel from the same tree — this is on top of it.)

### Fixed

- 🚨 **AN ADMITTED GUEST WAS NEVER LET IN.** The waiting room says *"you will
  join automatically once you are admitted"*, and nothing made that true: the
  admission broadcast updated the lobby entry and stopped there. A guest sat on
  that screen forever while the host's People panel showed them as let in — a
  sentence on screen that was a lie, which is the failure this package exists to
  prevent. It cannot be a re-render: a lobby token is minted with
  `canPublish: false` and `canSubscribe: false` (that is what makes the lobby
  real rather than a screen over a live microphone), so the cached token has to
  be discarded and a NEW one minted. `RoomEngine` gains `rejoin()`,
  `MeetingSessionOptions` gains `onAdmitted`, and `<MeetProvider>` wires the two
  together. The verdict is matched on the KNOCK identity — the one the host's
  admit RPC and the broadcast both carry — because a joiner in the lobby has no
  LiveKit participant identity yet.
- **A host control could render and then throw.** `useIsHost()` took the
  stronger of the token grant and the roster entry (0.4.0), while
  `meetings.ts::requireHostUser` and `room.ts::publishHostAction` still read the
  roster ALONE — so against a server that has not landed MRI-A11 the host saw
  every control and every one of them refused with "this participant is not a
  host". There is now ONE predicate, `effectiveRole(snapshot)` in `core/store.ts`,
  and the UI and the actions both ask it. A control that renders is a control
  that works.
- The note-taker's row in the meeting record said its own name twice
  ("Matrx note-taker · Matrx note-taker"). The badge now says what it DID.

### Guards

Three cases pin the admission re-join: a yes for this guest re-joins, somebody
else's verdict does not, and a denial is not a door. Proven RED against 0.4.0.
The refusal test for `publishHostAction` now denies BOTH carriers, which is what
the pair means.

**Consumer action: none** beyond taking `latest`.

## 0.4.1 — 2026-09-08

**A guest holding the link now gets the live meeting, not a frozen one.**
Register MRI-A12; resolves feedback `b25db4d4`.

### Fixed — the live feed reached members only, and two topics were fiction

Everything durable a meeting produces — live notes, decisions, action items,
the note-taker's state, the ending — arrived over Postgres Changes on
`communication.meet_*`. A **session-less guest cannot subscribe to any of it**:
`anon` holds no SELECT grant on `meet_notes`, `meet_transcript_segments`,
`meet_participants` or `meet_recordings`, and MRI-A10 ruled that widening it is
forbidden, because those are org-scoped tables and a meeting link is not
organization membership. So a guest saw whatever the join backfill gave them
and then nothing, forever, with no indication anything was missing.

`MEET_EVENTS.transcript`, `MEET_EVENTS.note`, `DATA_TOPICS.note` and
`DATA_TOPICS.transcript` were declared as the fallback that would have served
them, and were **emitted and subscribed by nothing**. They are gone.

### Added — `matrx.meet.*`: the platform's own voice in the room

The server now publishes each durable row into the LiveKit room the moment it
is written, where the room TOKEN is the authorization — the same plane captions
already ride. Four topics, one prefix, published only with a `roomAdmin` grant
that no participant token can hold:

| Topic | Payload | Written by |
|---|---|---|
| `matrx.meet.note` | `{ note: <meet_notes row> }` | every live-notes window and the wrap-up |
| `matrx.meet.note_taker` | `{ meeting_id, note_taker: { state, reason } }` | every note-taker state change |
| `matrx.meet.wrap_up` | `{ meeting_id, note_ids, segment_count }` | a finished wrap-up pass |
| `matrx.meet.ended` | `{ meeting_id, ended_at, trigger }` | ending a meeting, published before the room is deleted |

**A guest and a member converge on ONE code path.** A `matrx.meet.note` packet
carries the database row and is projected by `projectNote` — the very function
the Postgres Changes handler uses — then handed to `store.upsertNote`. Both
copies key on the row id, so a member who receives the note twice renders it
once; where the two could disagree the **database wins** and a room copy never
overwrites a record copy (D16). `MeetingNote` gains `source: "record" | "room"`
to carry that decision.

**Nothing a guest may not see travels.** The room copy of a note drops
`organization_id`, `created_by` and `assignee_user_id`; the assignee survives as
a display name (D18). The note-taker packet carries exactly the two keys a
surface renders. A guard fails on any user id in a payload.

New exports: `ROOM_EVENTS`, `ROOM_EVENT_PREFIX`, `isRoomEventTopic`,
`applyRoomEvent`, `projectNote`, `projectSegment`, `WrapUpAnnouncement`.
`useMeetAi()` gains `wrapUp`, and the assistant panel says the wrap-up is
written — absent, never a placeholder, when it was not announced.

### Consumer action

**None required.** The room-event handler is inside the room engine, so any
consumer already rendering `<MeetingRoom>` gets it on upgrade. Two named
removals: `MEET_EVENTS.transcript` / `.note` and `DATA_TOPICS.note` /
`.transcript` no longer exist — nothing consumed them, and referencing them is
now a type error rather than a subscription to silence.

### Known limit, stated rather than papered over

`matrx.meet.wrap_up` reaches a screen only while the room still exists. Ending
a meeting deletes the LiveKit room and the wrap-up pass runs afterwards in the
workflow-worker, so on the ordinary ending path there is nobody left in the room
to tell: the announcement is published, LiveKit reports no live room, and the
summary is read on the post-meeting record surface (0.4.0's `<MeetingRecord>`)
instead. `wrapUp === null` therefore means "not announced here", never "no
summary exists", and no surface renders it as the latter.

## 0.4.0 — 2026-09-08

**A host finally gets host controls, and a finished meeting finally has a
surface.** Register MRI-A11 and MRI-D2; feedback `7418ef78`.

### Fixed — MRI-A11: nobody was ever the host

`/meet/token` minted the LiveKit participant metadata with
`organization_id`, `user_id`, `is_agent` and `meeting_id` — **and no role**.
`projectParticipant` read the role out of exactly that blob, so `parseRole`
fell through to `"participant"` for every human in every meeting and
`useIsHost()` was false for everyone. The consequence, live, in a browser: no
lobby admission, no lock, no remove, no recording control, no note-taker
control and no "End meeting" — for the person who created the meeting.

The role is now the SERVER's word, decided once (`host_user_id` or a real `iam`
grant — organization membership is tenancy, never permission) and carried on
three carriers that cannot disagree because one statement produced all three:

- `role` on the `/meet/token` response (`host` | `participant` | `guest`);
- `role` and `can_record` in the token's participant metadata;
- `matrx.participant.role` and `matrx.participant.can_record` as LiveKit
  participant ATTRIBUTES, which are delivered on every roster update — the
  carrier a roster event cannot arrive without.

`useIsHost()` takes the stronger of the token grant and the roster entry, and
never anything else. New: `useRole()`, `useCanRecord()`, `useHostControls()`
(rank-checked mute/remove), `isHostRole()`, `MeetGrants` on the snapshot, and a
`guest` role in `ParticipantRole`/`ROLE_RANK`. `<PeoplePanel>` now renders
Host/Co-host/Guest badges for everyone and mute/remove for a host only;
removing a person and ending a meeting each name their consequence inline
before they happen (never `window.confirm`).

### Added — MRI-D2: the meeting after the meeting

An ended meeting rendered `<PreJoin>` — an offer to join a room that no longer
existed — while its summary, decisions, action items, live notes, transcript
and recording were reachable only from SQL. A durable link outlives the meeting
(R3), so it now resolves to the RECORD:

- `<MeetingRecordView>` — title, when and how long, who attended, the recording,
  the wrap-up summary, decisions, action items with their owners, the live-note
  windows, the transcript grouped into speaker blocks with timestamps, and a
  question box that works against the durable record long after the room closed.
- `<MeetingRoom>` routes to it automatically once `ended_at` is set, including
  mid-session: a meeting that ends while you are on the page flips to its record.
- `<MeetingList>` — every meeting including the ended ones, each linking to its
  record. A list that dropped ended meetings is what made the wrap-up unreachable.
- `core/record.ts`: `loadMeetingRecord`, `groupTranscript`, `recordingNotice`,
  `playableRecordingFileId`, `hasEnded`, `meetingWhen`; hooks
  `useMeetingRecord()` and `useMeetings()`; `repository.attendees()` and the
  `MeetingAttendee` type.
- **A refusal is rendered, never rendered as emptiness.** Only
  `meet_meeting_by_slug` is granted to `anon`; the four record tables are not.
  Each read is attempted independently and a refused one becomes a named
  sentence with what to do about it, so a guest sees what exists and why they
  cannot read it instead of a page implying a productive meeting produced
  nothing.
- The recording plays through the PLATFORM file id (`GET /files/{id}/download`)
  with the session's bearer token, on a click — never a signed URL, never an
  unrequested download of a video.

### Guards

Five cases pin the role reaching the roster from the attribute, from the
metadata, and not at all (an unknown role grants nothing) — three proven RED
against 0.3.3. A render test seats a host whose ROSTER entry says
"participant" while the token says host — the exact shape of the shipped defect
— and asserts the controls appear; proven RED against the roster-only read.
Eleven more assert who sees what, in both directions, for lobby admission,
recording, the note-taker and moderation. `record.test.ts` covers the guest
refusal path, partial refusals, note splitting, transcript grouping and the
honest recording states. On the aidream side, `test_meet_service.py` pins the
`role` response field, the attribute NAMES against this package's own source,
the role vocabulary against `types.ts`, and that `attributes` is minted as a
top-level LiveKit claim.

### Consumer action (C28)

1. **Take `latest` and the aidream release together.** The role fields are
   served by aidream from the same session; against an older server the package
   falls back to the `is_host` boolean it always read, so nothing breaks — but
   the guest/participant distinction and `can_record` only become real once the
   server is current.
2. **Pass the resolved meeting to `<MeetingRoom meeting={...}>`.** Optional, and
   REQUIRED for the guest lane of an ended meeting: only the
   `meet_meeting_by_slug` RPC is granted to `anon`, so a link-follower's
   `snapshot.meeting` is never populated and without this prop they get the
   pre-join screen for a finished meeting. Every consumer already resolves the
   slug through that RPC to get the room name — pass the row you already have.
3. **`MeetParticipant` gained a required `canRecord` field** and
   `ParticipantRole` gained `"guest"`. Consumers that build `MeetParticipant`
   literals (test fixtures, mocks) must add `canRecord`; consumers that switch
   exhaustively on `ParticipantRole` must handle `"guest"`.
4. **`MeetHost` gained `accessToken`** — supplied by `<MeetProvider>`, nothing to
   do unless you construct a `MeetHost` by hand.
5. Mount `<MeetingList />` wherever meetings are listed, so an ended meeting has
   a way in.

## 0.3.3 — 2026-09-08

**The second defect of the same class, found by the browser proof of 0.3.2
minutes after it published.** Register MRI-A9.

### Fixed

- **A participant who publishes nothing was ABSENT FROM THEIR OWN MEETING.**
  Every roster source in `core/livekit-driver.ts` is an EVENT
  (`ParticipantConnected`, the four track events, `LocalTrackPublished`…), and a
  listen-only participant alone in a room fires none of them. The projection
  therefore never ran: the People panel said *"0 in the meeting"* while the user
  was standing in it, there was no local tile, and — because host powers are
  read off the local participant's roster entry — **no host controls at all**,
  including "Start note-taker". The AI panel opened onto a meeting it could not
  act on.
- **Why it hid for so long, and why 0.3.2 exposed it:** the join always
  published a microphone, so `LocalTrackPublished` performed the initial
  projection by accident. It was never a rule, only a side effect of publishing
  media — and the moment listen-only attendance became possible it stopped
  happening. The fix is to make it a rule: `connect()` projects the roster once,
  itself, the moment the transport is up.

### Guards

A `livekit-client` stand-in drives `createLiveKitDriver().connect()` with no
event, no track and nobody else in the room, and asserts exactly one roster
projection containing the local participant. Proven RED against 0.3.2, where
the handler was never called at all.

**Consumer action: none** beyond taking `latest`.

## 0.3.2 — 2026-09-08

**LISTEN-ONLY ATTENDANCE. Being in a meeting no longer depends on owning a
working microphone.** Register `meet-realtime-intelligence` MRI-A9; feedback
`e7f8bf4e-2085-4c8c-9ed3-d2a4148f1624`.

### Fixed

- **A denied, absent, or busy microphone ABORTED THE JOIN.** `connect()` awaited
  `setMicrophoneEnabled(true)` as the final step of joining, inside the same
  `try`, so a `NotAllowedError` from `getUserMedia` did not produce a muted
  participant — it failed the join, stamped the room `failed` with a device
  remedy, and dropped the user back to `<PreJoin>` having never entered a room
  they were already connected to. Every in-room surface — tiles, captions, live
  notes, the Q&A panel, the note-taker consent notice — was therefore
  unreachable to anyone whose browser refuses capture. That is not an edge case:
  a locked-down work profile, a conference-room machine, a mic already held by
  another app, and a person who simply wants to listen all land there, and every
  competing product admits them. It is also why the in-room AI panel had never
  once been seen in a browser (MRI-A5's named gap).
- **Fixed as the CLASS, not the instance.** Connecting and publishing are now
  two operations. The engine reaches `connected` the moment the transport is up;
  media is attempted afterwards, per track, through ONE function
  (`enableTrack`) that the join and the in-room controls share. A refusal is
  recorded as state with the browser's own reason and remedy — never thrown,
  never swallowed.
- **A mid-meeting device failure no longer blanks a live room.**
  `setMicrophoneEnabled` / `setCameraEnabled` used to stamp `phase: "failed"` on
  a device error, so clicking Unmute while another app held the mic would tear
  down a working meeting for everyone on that screen. They now record the same
  honest track state and leave the room alone.
- **A denied camera no longer takes the microphone down with it** on a video
  join, and vice versa. They are independent tracks and now fail independently.

### Added

- `snapshot.media` — `{ microphone, camera }`, each `{ enabled, unavailable }`,
  where `unavailable` carries `{ code, message, remedy }` from the package's own
  device-error translator. This is what to read to decide whether to tell
  someone they cannot be heard; the roster's `micEnabled` reflects the media
  plane and says `false` with no reason attached.
- `JoinRequest.microphone` (default `true`). `false` is a deliberate
  listen-only join — no device is touched at all.
- `<PreJoin>` gains a **"Join with microphone"** toggle beside "Join with
  video", so listen-only is a choice a user can make BEFORE the browser makes it
  for them.
- `<AttendanceNotice />`, rendered by `<MeetingRoom>`: *"You're attending
  without a microphone — others can't hear you"*, with the refusal's remedy
  beneath it when the device was refused rather than declined. The sentence has
  ONE definition (`LISTEN_ONLY_NOTICE`) so a host rendering its own chrome
  cannot drift from the package's wording.
- `useLocalMedia()`, `useListenOnlyNotice()`, and the core helpers
  `isListenOnly()`, `listenOnlyNotice()`, `LISTEN_ONLY_NOTICE`, `IDLE_MEDIA`,
  `IDLE_TRACK`, `unavailableFrom()`, plus the types `LocalMediaState`,
  `LocalTrackState`, `MediaUnavailable`.
- The microphone control's label now states the refusal
  (*"Unmute — Allow camera and microphone access…"*). It stays clickable on
  purpose: plugging in a headset and clicking again is the fix, and it runs the
  identical code path the join ran.

### Guards

Nine tests, six of them proven RED against 0.3.1 and GREEN after — including
the one that had to exist: a **jsdom render** that reproduces the Browser
pane's refusal (`driverFactory` whose `setMicrophoneEnabled` rejects with
`NotAllowedError`), clicks Join, and asserts the room is entered, the control
bar renders, the notice appears verbatim with its remedy, and `<PreJoin>` is
gone. A seventh test pins that a REAL join failure (transport) still fails the
join, so the fix cannot rot into swallowing errors.

**Consumer action: none** beyond taking `latest`. `snapshot.media` is additive;
`JoinRequest.microphone` is optional. The only behaviour change a consumer can
observe is that `setMicrophoneEnabled` / `setCameraEnabled` no longer reject on
a device error — read `snapshot.media.<track>.unavailable` instead, which
carries strictly more than the thrown `MeetError` did.

## 0.3.1 — 2026-09-08

**Two defects found by WALKING THE REAL PIPE, minutes after 0.3.0 published.**
`POST /api/v1/meet/intelligence/ask` was called against the production server
with a real admin session and a real meeting; the captured stream is now a
verbatim fixture in `src/core/ai.test.ts`, and both defects were proven
failing-then-passing against it. Neither was visible to any test written from
the documented vocabulary — which is the lesson, again.

### Fixed

- **A phase rendered as `[object Object]`.** The canonical emitter sends
  `{"event":"phase","data":{"phase":"generating"}}`, and the reader looked only
  for `text`/`content`/`delta`/`t`. The AI panel showed that string while an
  answer was being built.
- **The coverage numbers never arrived.** `InfoPayload` is
  `{code, system_message, user_message, metadata}`, so `segments_used` /
  `segments_total` / `transcript_truncated` / `speakers` ride
  `data.metadata` — not `data`. `MeetAiAnswer.coverage` was silently `null`
  for every real answer, so "Answered from N of M transcript lines" never
  rendered.

### Also

- **A server remedy written inline is split out.** Meet's own errors read
  "<what happened>. Fix: <what to do>."; the fix now lands on its own line in
  the panel instead of being buried mid-paragraph. Live example, verbatim from
  production: *"Meeting … has no final transcript segments yet, so no question
  about it can be answered from the record."* + *"wait for the note-taker to
  transcribe some speech — or start it if it is not in the room."*

No API changed. **Consumer action: none** beyond taking `latest`.

## 0.3.0 — 2026-09-08

**BREAKING. The meeting assistant is now SERVER-OWNED.** A host no longer names
an agent, and no transcript ever leaves this client. Live captions, durable
notes, an honest note-taker state and a package-owned consent notice land with
it (register `meet-realtime-intelligence` MRI-A5; DECISIONS D2, D7, D9, D10,
D16, D18, D19).

### Removed

- **`MeetAgents`, `MeetAiCapability`, `MeetAiResult`, `transcriptForAgent`, and
  the `agents` / `transport` props on `<MeetProvider>`.** Through 0.2.1 a host
  injected four database row ids and this package ran the agent itself, sending
  the whole transcript up as `variables`. Which agent answers is a Mandate
  binding the SERVER resolves (`meet.live_notes`, `meet.live_intelligence`,
  `meet.wrap_up`), so an organization rebinds a job in the Mandate admin with no
  package release and no host change — and a capability can no longer vanish
  because somebody did not fill in a config line.
- **`projectNote` moved** out of `core/ai.ts` into the repository, where it now
  reads `assignee_user_id`, `metadata.assignee` (D18), `metadata.role` and
  `metadata.window` instead of dropping them.
- **`store.ingestTranscript` and `store.addNote`** — replaced by
  `ingestCaption` / `ingestRecord` (captions and durable rows are different
  things, D16) and `upsertNote` (a re-run of the wrap-up rewrites the same row
  ids by design, and appending showed every note twice).

### Added

- **Live Q&A against `POST /api/v1/meet/intelligence/ask`.** The body is
  `{meeting_id, question}` and nothing else; the answer streams as NDJSON chunks
  and renders token by token, with the server's phase line and its own
  measurement of what the answer was built from (segments used / total /
  truncated). No transcript, no roster, no agent id crosses the wire.
- **Live captions (D16).** The room engine consumes the LiveKit
  `lk.transcription` text streams — interim and final, keyed by `lk.segment_id`,
  attributed by the stream's speaker identity rather than by the publisher (the
  note-taker publishes every one of them) — and renders them over the stage.
- **Durable reconciliation.** A Postgres Changes subscription on the meeting's
  `meet_transcript_segments`, `meet_notes` and `meet_meetings` rows, with a
  reconnect backfill that re-reads all three. **Captions are hints; segments are
  truth**: a durable row REPLACES the caption it supersedes, matched on the
  caption id the worker carries into the row and, failing that, on speaker +
  text. A caption never overwrites a row, however late it lands.
- **An honest note-taker state.** `absent | joining | transcribing |
  handoff_pending | ended` derived from the meeting row AND the room's roster —
  presence beats a stale row, and a row that says `transcribing` while the agent
  is not in the room reads as **re-joining** (D9), never as a transcription that
  is silently not happening. A refused dispatch renders the server's `reason`
  and `remedy` **verbatim** (D7) and stays on screen as state, not only as a
  thrown error.
- **The consent notice is in the package now (D10).** Whenever an agent
  participant is present — `agent:note_taker:*`, the
  `matrx.agent.consent_notice` attribute, or the token's `is_agent` — every
  participant sees a banner, and the People panel carries the row
  "Matrx note-taker · recording notes". There is no prop that hides it.
- **`MEET_ROUTES.intelligenceAsk` and `MEET_ROUTES.meetingEnd`**, plus an
  "End meeting for everyone" host control wired to `/end`. That route is served
  by MRI-A6 and is **not on the server yet**: a 404 renders the sentence
  *"Ending a meeting is not available on this server yet"* — never a dead
  button, never a silent no-op, and never a meeting that looks ended and is not.
- New exports: `noteTakerStatus`, `noteTakerLabel`, `isNoteTaker`,
  `noteTakerParticipant`, `noteTakerIdentity`, `NOTE_TAKER_IDENTITY_PREFIX`,
  `CONSENT_NOTICE_ATTRIBUTE`, `mergeCaption`, `mergeRecord`, `captionSegment`,
  `readableTranscript`, `normalizeSpoken`, `readServerRefusal`,
  `TRANSCRIPTION_TOPIC`, `TRANSCRIPTION_ATTRIBUTES`, `END_MEETING_NOT_SERVED`;
  components `<Captions>` and `<ConsentNotice>`; hooks `useEndMeeting` and
  `useConsentNotice`.

### Changed

- `MeetHost.ai` is **never null**. It was null unless a host injected a
  transport and four ids, which is why the assistant silently did not exist in
  the first real consumer.
- `MeetParticipant` gains `attributes` (the LiveKit participant attributes);
  `TranscriptSegment` gains `source` (`caption` | `record`) and `captionId`;
  `MeetingNote` gains `assigneeDisplayName`, `role` and `window`.
- `MeetingSession` takes `baseUrl` + `accessToken`, gains `watchMeeting`, and
  `endForEveryone()` now goes through the server and returns
  `{ endedAt, served }` instead of calling the `meet_end_meeting` RPC directly
  (which ended the row while LiveKit kept the room open).

### Proof

165 tests (18 files) — new files `transcript.test.ts` (9), `note-taker.test.ts`
(9), a rewritten `ai.test.ts` (12) that asserts at the WIRE that no `agent_id`
and no transcript are ever sent, a room-engine caption test, and 10 render tests
covering every note-taker state, the streamed answer, the verbatim refusal, the
consent notice and the honest 404. Gates: typecheck, publint, the packed-tarball
canary through both loaders (now pinning the two new routes, the caption topic,
caption-vs-record precedence and the note-taker state machine), and the sibling
DAG. aidream's contract test reads `MEET_ROUTES` out of this source and now
carries a two-way `NOT_YET_SERVED` guard for `/end` — proven failing-then-passing.

### Consumer action (C28, D19)

| Was | Do this instead |
|---|---|
| `<MeetProvider agents={...} transport={...}>` | Delete both props. The assistant is server-owned; nothing replaces them. |
| A host module resolving `meet.*` mandates to agent ids (`useMeetIntelligences`, `meetMandates`) | Delete it. The server resolves the Holder at run time. |
| `import { MeetAgents, MeetAiCapability, MeetAiResult, transcriptForAgent } from "@ai-matrx/meet"` | Gone. `MeetAiAnswer` is the answer shape; there is no capability enum. |
| `ai.available()` / `ai.isAvailable(cap)` / `ai.summarize()` / `ai.extractActionItems()` | Gone. Summary, decisions and action items are durable rows the panel renders; the wrap-up runs server-side when the meeting ends. |
| A host-written note-taker consent banner reading `participant.isAgent` | Delete it. `<MeetingRoom>` renders the package's own notice for every participant. |
| `store.ingestTranscript(...)` / `store.addNote(...)` | `store.ingestCaption(...)` / `store.ingestRecord(...)` / `store.upsertNote(...)`. |
| `session.endForEveryone()` returning a `MeetingRecord` | It returns `{ endedAt, served }`; render `END_MEETING_NOT_SERVED` when `served` is false, or just use the built-in host control. |
| A fixture building a `MeetParticipant` / `TranscriptSegment` / `MeetingNote` by hand | Add `attributes`, `source` + `captionId`, and `assigneeDisplayName` + `role` + `window`. |

Nothing else changes: `<MeetProvider>`'s identity props, `<MeetingRoom>`,
`<CallButton>`, `<IncomingCallHost>` and the invitation handlers are untouched.

## 0.2.1 — 2026-09-08

**Bug-fix release. The first real consumer could not join a room on 0.2.0** — every
one of the four defects below was found by RUNNING the package on
aimatrx.com against the production backend, not by any gate this package had
(register `meet-realtime-intelligence` MRI-D1 → MRI-A5a). Nothing new is added
and nothing is removed; the public API is unchanged except that the `client`
prop got LOOSER.

### Fixed

- 🚨 **BLOCKER — every room crashed at `<PreJoin>` before a token was ever
  requested.** `createDeviceManager().snapshot()` built a fresh object literal
  on every call, and `useDeviceSnapshot()` hands it to `useSyncExternalStore`
  as `getSnapshot`. React compares snapshots by IDENTITY, so every render
  reported a change: *"The result of getSnapshot should be cached to avoid an
  infinite loop"* → *"Maximum update depth exceeded"* → the host's error
  boundary. `<MeetingRoom>` always renders `<PreJoin>` first, so BOTH the
  authenticated and the guest lane died there. The snapshot is now memoized
  exactly the way `createMeetStore()` always did it: one object, replaced only
  when a field actually moves.
  **Fixed as a class, not an instance** — `src/core/snapshot-identity.test.ts`
  enumerates EVERY snapshot source this package hands to `useSyncExternalStore`
  (today: the store and the device manager) and proves each stable-until-changed
  AND changed-on-change; a new one is added to that list in the same commit.
  A second guard renders `<MeetingRoom>` for real in jsdom and fails on the
  React warning. Both proven RED (`Maximum update depth exceeded`) then GREEN.
- **`<IncomingCallHost/>`, `<CallButton/>` and every other component threw
  while the provider was inert.** `<MeetProvider>` is deliberately inert until a
  client, an organization and an identity are all real — which is every server
  render, every signed-out visitor, and every moment before the active
  organization hydrates — but the hooks called `useRequiredMeetHost()`, so the
  README's own three-line integration returned a **500 on every route** for all
  of those visitors. No hook in `react/hooks.ts` throws for an inert provider
  any more: an absent runtime is reported as DATA (empty lists, `canCall:
  false`, `available: false`, `canAdmit: false`), so the ambient components
  (`<IncomingCallHost>`, `<CallButton>`, `<RecordingIndicator>`) render
  **nothing**, and the entry components (`<MeetingRoom>`, `<PreJoin>`,
  `<ScheduleMeeting>`) render an **honest sentence** and no dead controls. Only
  an ACTION invoked with no runtime fails, and it fails loudly with a remedy.
  Guarded by six render tests, proven RED then GREEN.
- **`<MeetProvider client>` and `createMeetRepository({ client })` sent tsc into
  `TS2589`.** The public boundary was the rich internal `SupabaseLike`; checking
  a host's real `SupabaseClient<Database>` (built from generated types) against
  it makes tsc chase PostgREST's schema machinery until it bails with *"Type
  instantiation is excessively deep and possibly infinite"* — so the only way to
  pass the client a host ALREADY HAS was a cast this package's own header
  forbids. Both public doors now take the shallow
  `MessagingSupabaseClient` (method names and arity, results as `unknown`); the
  rich shape is still the internal contract and we narrow to it ONCE, in-package.
  This is the lesson `@ai-matrx/messaging` 0.9.0 recorded for the identical
  defect, finally applied here. `src/core/supabase-shape.test.ts` gained the
  consumer's leg — a `SupabaseClient<GeneratedDatabase>` assigned to both doors
  with no cast — proven RED (the exact `TS2589`) then GREEN.
- **`dist/index.d.ts` and `dist/react.d.ts` re-declared every branded type.**
  Two independent tsup dts builds each inlined the whole module graph, including
  its own `declare const brand: unique symbol` — and a branded type is nominal
  per DECLARATION, so `UserId` from `@ai-matrx/meet` was not assignable to
  `UserId` from `@ai-matrx/meet/react`, for every branded type in the package.
  Both entries are now built in ONE dts pass, so `react.d.ts` IMPORTS the core's
  declarations instead of restating them (`react.d.cts` correctly points at
  `./index.cjs`). The packed-tarball canary now asserts exactly one brand
  declaration in the core and none in the React entry — proven RED then GREEN
  against the old config.

### Also

- The package's own `@ai-matrx/*` dependencies were resolving to a stale install
  (messaging 0.1.1 against a published 0.10.4); refreshed to latest per THE
  LATEST LAW, which is what made `MessagingSupabaseClient` reachable.
- New structural class `.mx-meet__empty` in `styles.css` for the honest inert
  state. Tokens only; no hardcoded color.

### Consumer action (C28)

**Delete your wrappers — all four of them.** A consumer on 0.2.0 was forced to
work around every defect above; each workaround is now dead code:

| Workaround | Do this instead |
|---|---|
| A host guard that mounts `<IncomingCallHost/>` / `<CallButton/>` only when `useMeetHost()` is non-null | Mount them directly, exactly as the README says. They render nothing on their own while the provider is inert. |
| A `client` cast (`supabase as never`, or a `meetClient()` helper) | Pass your `SupabaseClient` straight into `<MeetProvider client>` / `createMeetRepository`. |
| A house rule of "import everything from `@ai-matrx/meet/react`" to dodge the branded-type split | Still a fine habit for a React file, but no longer required: the two entries share one set of declarations. |
| Anything that avoided rendering `<MeetingRoom>` / `<PreJoin>` because they crashed | Render them. |

No API was removed and no signature narrowed, so nothing else has to change.

## 0.2.0 — 2026-09-07

> **Release note:** this release also carries the `x-organization-id` header fix
> written up below as 0.1.3, which had not yet been published when this landed.

**Consumer action (C28).** Three exports MOVED OUT of this package to
`@ai-matrx/kit/format`. Update the import; the behaviour is identical and every
assertion this package's suite made is re-asserted in kit's.

| was | now |
|---|---|
| `import { formatDuration } from "@ai-matrx/meet"` | `import { formatDurationMs } from "@ai-matrx/kit/format"` |
| `import { getInitials } from "@ai-matrx/meet"` | `import { getInitials } from "@ai-matrx/kit/format"` |
| `import { avatarPaletteIndex } from "@ai-matrx/meet"` | `import { avatarPaletteIndex } from "@ai-matrx/kit/format"` |

Note the rename: `formatDuration` -> **`formatDurationMs`**. The unit is now in
the name, because the fleet's twins variously took milliseconds, seconds and
minutes behind an identical signature. This package's copy took milliseconds,
so `formatDurationMs` is a drop-in.

**Why.** Duplication census finding H1 (2026-09-07): these three bodies were
byte-for-byte duplicated in `@ai-matrx/messaging`, and because two packages
claimed them, ~65 host twins across six repos had no correct owner to point
at — no surface should take a dependency on the video-calling package to print
a duration or an avatar's initials. They now live in `@ai-matrx/kit`, which has
zero sibling dependencies and sits at the bottom of the DAG.

`participantSummary`, `callStateLabel` and `countLabel` — the genuinely
call-shaped formatters — stay here. Nothing is re-exported: this package
imports kit directly, and so should you.

New dependency: `@ai-matrx/kit` (`latest`, per THE LATEST LAW).

## 0.1.3 — 2026-09-07

### Fixed
- **Every aidream request now names the organization in an `x-organization-id`
  HEADER, not only in the body.** Found by probing the live routes the day the
  server half landed: aidream's org-admission middleware refuses an
  authenticated request that does not say which organization it is acting in,
  so every authenticated `token` / `recording` / `agent` call from 0.1.2 was
  rejected with `400 organization_required` before the meet router ever ran.
  The body field is unchanged (the service reads it); the header is what gets
  the request through the door. Guests send it too — a guest still belongs to
  the meeting's organization. `RecordingControllerOptions` gains a required
  `organizationId`; `<MeetProvider>` already had it and passes it through, so
  no consumer change is needed. Guarded by a regression test, proven
  failing-then-passing.

### Context
- The aidream server half and the `communication.meet_*` schema both landed
  2026-09-07; see this package's FEATURE.md § Deliberately NOT in this package.

## 0.1.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.1.1..npm/meet/v0.1.2 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.1.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/meet/v0.1.0..npm/meet/v0.1.1 -- apps/shared/meet`).
No source changes intended and no consumer action required.

## 0.1.0 — 2026-08-31

First release. Enterprise, AI-native calls and meetings for every Matrx client, built to the
ratified work order (`common-docs/systems/communications/meet/HANDOFF.md`, D1–D8, R1–R8).

### Added

- **`/core` — the framework-free half.** The token client (single-flight, refresh at 80% of a
  token's life, guest identities, and a typed refusal for every server verdict), the device
  manager (hidden-label reporting, the vanished-device fallback that announces itself, a probe
  stream that is always stopped), the room engine over a LiveKit driver seam (join sequencing,
  the lobby as a phase, visible reconnects, the in-room data control plane, idempotent
  teardown), the call center (dual-leg delivery, ring timeouts, busy, answered-elsewhere, a
  validated state machine), the meeting session (lobby admission by RPC, rank-checked host
  powers, durable links), the recording controller (org policy, enforced consent, no fake
  indicator), the AI seam (note-taker dispatch, live Q&A, post-meeting pipeline — all through
  the platform agent system), the stage arithmetic (stable ordering, paging, screen-share
  priority), and the meet data contract.
- **`/react` — the whole product UI.** `<MeetProvider>`, `<MeetingRoom>`, `<PreJoin>`,
  `<ControlBar>`, `<Stage>`, `<ParticipantTile>`, `<LobbyPanel>`, `<ChatPanel>`, `<PeoplePanel>`,
  `<AiPanel>`, `<IncomingCallHost>`, `<CallButton>`, `<ScheduleMeeting>`, `<RecordingIndicator>`,
  eleven hooks, and inlined SVG icons (no icon-library dependency, C19).
- **Package-owned CSS** — `./styles.css` (structural, zero hardcoded colors) and `./tokens.css`
  (default values a host overrides with its brand), with `dvh`, safe-area insets, 44px touch
  targets, 16px inputs, and a reduced-motion guard proven by the tarball canary.
- **A packed-tarball canary** that installs the real artifact into an empty project, exercises
  both loaders, pins the DB and route contracts, and proves `livekit-client` is NOT in the
  import graph — the media SDK is reached only by the dynamic import inside `connect()`.

### Consumer action

This is a first release, so nothing is breaking. To adopt it:

1. `pnpm add @ai-matrx/meet` (`"latest"`, per THE LATEST LAW). `livekit-client` comes with it and
   is loaded lazily at connect time — do not add it to `serverExternalPackages` or any bundler
   `external` list, or the dynamic import cannot resolve.
2. Import the stylesheets **in this order**: `@ai-matrx/meet/tokens.css`, then your brand
   overrides, then `@ai-matrx/meet/styles.css`.
3. Mount ONE `<MeetProvider client={supabase} baseUrl={aidreamUrl} organizationId={orgId}
   userId={userId} accessToken={getAccessToken}>` high in the tree, and render
   `<IncomingCallHost />` inside it so calls ring on every surface.
4. Render `<MeetingRoom roomName={...} />` on your meeting route, and `<CallButton person={...} />`
   wherever a person appears.
5. Register the invitation handlers on your `<MessagingProvider actions={[...]}>` —
   `createCallInviteHandler` and `createMeetingInviteHandler` — so a call invitation is an
   actionable message rather than only an ephemeral ring.

🚨 **Two server-side prerequisites are NOT yet built** and are tracked in `FEATURE.md`
§ *Deliberately NOT in this package*: the aidream meet router (token minting, Egress control,
LiveKit webhooks, the LiveKit Agents note-taker) and the `communication.meet_*` schema in Matrx
Main. Until both land, the package fails LOUDLY at the token call and at every repository read —
with a remedy naming this file — rather than pretending to work.


