# Share a family workspace

SDK 0.7.0 provides shared encrypted workspaces for household members. Each person
uses their own Seige account. The app remains a static frontend; Seige enforces
membership, installation permissions, key epochs and revisions on the server.
Calendar is not part of this API.

## Manage membership in Seige

Check `seige.capabilities()` before offering family controls. Handle errors
separately from a null `familySharing` capability. From a user action, call
`seige.households.manage()`. The Seige window can create a household, invite a
recipient username, accept an invitation and approve member keys. Select the
households whose names and scoped membership IDs this app may see, then approve.

The SDK receives no invitation credential or global account directory. The owner
must approve a new member's keys. If necessary, the owner can use **Update shared
keys** in Seige to deliver existing workspace keys to an approved member.

```js
manageButton.onclick = async () => {
  const managed = await seige.households.manage();
  if (managed.error) return showError(managed.error);
  const result = await seige.households.list();
  if (result.error) return showError(result.error);
  renderHouseholdChoices(result.data);
};
```

## Approve a workspace for each installation

The household owner calls `shared.create({householdId,name})` from a separate
button. Creation opens a Seige approval window. Members discover this app's
resources with `shared.list({householdId})`, then call
`shared.requestAccess({resourceId,access:["read","write"]})` from a user action.
A read-only installation requests `["read"]`. Each browser approves separately.
Membership does not grant app access, and personal folders remain separate.

```js
const opened = await seige.shared.open({ resourceId });
if (opened.error) return showError(opened.error);
const workspace = opened.data;
const snapshot = await workspace.read();
if (snapshot.error) return showError(snapshot.error);
renderRecords(snapshot.data.records);
```

Record payloads are Uint8Array values. The SDK verifies the writer signature,
server receipt and encrypted batch before returning them. `record.memberId` is
the verified publisher's household-scoped identity. Editable labels inside a
payload do not prove who originally wrote or requested an item.

## Publish with revisions and stable mutation IDs

```js
const result = await workspace.mutate({
  mutationId: crypto.randomUUID(),
  expectedRevision: snapshot.data.revision,
  schemaVersion: 1,
  records: [{ objectId, payload: new TextEncoder().encode(JSON.stringify(edit)) }]
});
if (result.error) return showError(result.error);
showPublication(result.data);
```

Only `published` confirms server acceptance. The SDK saves one encrypted exact
signed packet per workspace before sending it. Use the same mutation ID for a
retry of the same contents. `pending` needs reconnection; `conflict` needs a new
read and deliberate application-level merge. The SDK does not merge records or
SQL. Keep a draft until the result is resolved. Mutations support at most 100
records and 256 KiB plaintext. Set `deleted:true` for a verified tombstone.

`workspace.pending()` returns decrypted queued records for review or export.
`workspace.retry()` replays the exact packet. After reviewing a conflict, pass
`replacePending` with its known mutation ID to explicitly replace it with a merged
mutation. Never automatically discard a conflicting edit.

## Read after invalidation

`shared.subscribe({resourceId,cursor,signal})` returns a Result containing an
AsyncIterable. Iterate inside a try/catch, reread authoritative records after
hints, and reconnect using the last loaded revision. Hints contain no content.
Defer applying new data while the person is editing. Snapshot pagination can
reject a concurrent revision change; retry the full read. Close handles and
clear displayed records when accounts or access change.

## Recover after removal

Removal immediately blocks the member's access and queued publication, and
freezes new workspace writes. The owner calls `shared.rotate({resourceId})`
from a user action to distribute a fresh key to remaining members. Every browser
then approves access again. Old copied content and old keys cannot be recalled.

Retain queued changes until the person makes an explicit recovery choice.
`shared.pending({resourceId})` returns local queue metadata even after removal;
`shared.discardPending({resourceId,mutationId})` explicitly discards it. Decrypting
or exporting queued records requires approved keys. Keep personal mode available
through an explicit choice rather than silently switching a failed shared view.

## Related guides

- [Service capabilities](/guides/check-sdk-capabilities/)
- [Full SDK reference](/developers/sdk-reference.md)
- [Sign in without a backend](/guides/sign-in-without-a-backend/)
