@book.dev/ui 1.60.0 → 1.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{EmojiGrid-xK5mPJPo.js → EmojiGrid-THzuvC4K.js} +75 -84
- package/dist/blockeditor/BlockEditor.d.ts +16 -0
- package/dist/blockeditor/EmojiMenu.d.ts +17 -0
- package/dist/blockeditor/__tests__/EmojiMenu.test.d.ts +1 -0
- package/dist/blockeditor/__tests__/readOnly.test.d.ts +1 -0
- package/dist/blockeditor/__tests__/titleHandoff.test.d.ts +1 -0
- package/dist/blockeditor/__tests__/triggerMenus.test.d.ts +1 -0
- package/dist/blockeditor/kit/KitFrame.d.ts +5 -0
- package/dist/blockeditor/kit/lock.d.ts +14 -0
- package/dist/components/LastEditedBy.d.ts +11 -0
- package/dist/components/OnboardingNudge.d.ts +11 -0
- package/dist/components/ShareDialog.d.ts +25 -0
- package/dist/components/__tests__/lastEditedBy.test.d.ts +1 -0
- package/dist/components/__tests__/membersSettings.test.d.ts +1 -0
- package/dist/components/__tests__/sharingSettings.test.d.ts +1 -0
- package/dist/components/settings/AccountSettings.d.ts +4 -0
- package/dist/components/settings/AccountSwitcher.d.ts +12 -0
- package/dist/components/settings/MembersSettings.d.ts +1 -0
- package/dist/components/settings/SharingSettings.d.ts +9 -0
- package/dist/emoji-Bmft6RPl.js +11 -0
- package/dist/{format-CLQoRoYP.js → format-D9FO3jG9.js} +114 -114
- package/dist/i18n/messages/en.d.ts +169 -0
- package/dist/index.js +7238 -5976
- package/dist/lib/__tests__/useCanWrite.test.d.ts +1 -0
- package/dist/lib/hud.d.ts +2 -2
- package/dist/lib/sidebarStyles.d.ts +7 -0
- package/dist/lib/useCanWrite.d.ts +45 -0
- package/dist/{lucideIcons-B6pmC-WQ.js → lucideIcons-Dv5yzk2L.js} +1916 -1016
- package/dist/providers/AccountProvider.d.ts +47 -7
- package/dist/providers/ForwardingProvider.d.ts +19 -0
- package/dist/providers/PlatformLibraryProvider.d.ts +20 -0
- package/dist/providers/__tests__/AccountProvider.test.d.ts +1 -0
- package/dist/providers/__tests__/forwardingAudience.test.d.ts +9 -0
- package/dist/providers/forwardingAudience.d.ts +165 -0
- package/dist/screens/pageChrome.d.ts +11 -0
- package/dist/style.css +1 -1
- package/dist/{toHtml-BoPr8Ce4.js → toHtml-DeWpCU0o.js} +2 -2
- package/package.json +7 -2
|
@@ -12,23 +12,47 @@ import React, { PropsWithChildren } from 'react';
|
|
|
12
12
|
*
|
|
13
13
|
* Sync: pull on connect / app open (remote wins), then push the
|
|
14
14
|
* `{preferences, workspaces}` blob on local change (debounced, last-writer-wins).
|
|
15
|
+
*
|
|
16
|
+
* Multi-account (OB-194): the client holds a **list** of connected accounts and
|
|
17
|
+
* an **active** one. Sign-in *adds* an account rather than replacing the current
|
|
18
|
+
* one; the active account is the identity presented to the data server and the
|
|
19
|
+
* one whose settings sync. Each account's device token is stored separately and
|
|
20
|
+
* namespaced (OS keychain on desktop, namespaced `localStorage` on web/dev) —
|
|
21
|
+
* no cross-account leakage. The single-account fields below keep reflecting the
|
|
22
|
+
* ACTIVE account, so a lone account behaves exactly as it did before.
|
|
15
23
|
*/
|
|
16
24
|
export type AccountStatus = 'disconnected' | 'connecting' | 'syncing' | 'connected' | 'error';
|
|
25
|
+
/** One connected account in the multi-account list. Carries only non-secret
|
|
26
|
+
* metadata — the device token lives in the per-account secret store. */
|
|
27
|
+
export interface ConnectedAccount {
|
|
28
|
+
/** Stable local id for this account slot (namespaces its token + index row). */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Display label — the active-persona email, else the name/account host. */
|
|
31
|
+
name: string;
|
|
32
|
+
/** The active-persona email (lowercased) when the identity JWS asserts one. */
|
|
33
|
+
email: string | null;
|
|
34
|
+
/** The account service base URL this account signed in to. */
|
|
35
|
+
accountUrl: string;
|
|
36
|
+
/** Connection status. The ACTIVE account tracks the live status; the others are
|
|
37
|
+
* dormant (reported as `connected` until they are made active). */
|
|
38
|
+
status: AccountStatus;
|
|
39
|
+
}
|
|
17
40
|
interface AccountContextValue {
|
|
18
41
|
status: AccountStatus;
|
|
19
42
|
connected: boolean;
|
|
20
|
-
/** The device bearer token, for same-app account API
|
|
21
|
-
* POST /api/sites). Null when disconnected. Treat as
|
|
43
|
+
/** The device bearer token of the ACTIVE account, for same-app account API
|
|
44
|
+
* calls (e.g. forwarding's POST /api/sites). Null when disconnected. Treat as
|
|
45
|
+
* a secret. */
|
|
22
46
|
token: string | null;
|
|
23
47
|
/** The label this device registers under (shown in the account dashboard). */
|
|
24
48
|
deviceName: string;
|
|
25
|
-
/** ISO timestamp of the last successful server sync, or null. */
|
|
49
|
+
/** ISO timestamp of the active account's last successful server sync, or null. */
|
|
26
50
|
lastSyncedAt: string | null;
|
|
27
51
|
/** A human-readable error from the last failed action, or null. */
|
|
28
52
|
error: string | null;
|
|
29
|
-
/** The account service base URL (for an "open dashboard" link). */
|
|
53
|
+
/** The active account's service base URL (for an "open dashboard" link). */
|
|
30
54
|
accountUrl: string;
|
|
31
|
-
/** Start the deep-link sign-in flow. */
|
|
55
|
+
/** Start the deep-link sign-in flow (additive — see {@link addAccount}). */
|
|
32
56
|
signIn: () => void;
|
|
33
57
|
/** Complete sign-in from a manually pasted code — the dev/fallback path for when
|
|
34
58
|
* the `openbook://` deep link can't fire (the user dismisses the "open app?"
|
|
@@ -37,10 +61,26 @@ interface AccountContextValue {
|
|
|
37
61
|
submitCode: (raw: string) => void;
|
|
38
62
|
/** Abandon a pending sign-in (returns to disconnected when not yet connected). */
|
|
39
63
|
cancel: () => void;
|
|
40
|
-
/** Forget the
|
|
64
|
+
/** Forget the ACTIVE account's token (does not revoke it server-side — do that in
|
|
65
|
+
* the dashboard). If other accounts remain, switches to one of them. */
|
|
41
66
|
signOut: () => void;
|
|
42
|
-
/** Pull-then-push a reconciliation now. */
|
|
67
|
+
/** Pull-then-push a reconciliation now (for the active account). */
|
|
43
68
|
syncNow: () => void;
|
|
69
|
+
/** Every connected account, in connection order. */
|
|
70
|
+
accounts: ConnectedAccount[];
|
|
71
|
+
/** The id of the active account, or null when none is connected. */
|
|
72
|
+
activeAccountId: string | null;
|
|
73
|
+
/** Make `id` the active account: presents its identity, syncs its settings. */
|
|
74
|
+
setActiveAccount: (id: string) => void;
|
|
75
|
+
/** Start the sign-in flow to ADD an account (alias of {@link signIn}; the flow
|
|
76
|
+
* is additive — a new sign-in never evicts the accounts already connected). */
|
|
77
|
+
addAccount: () => void;
|
|
78
|
+
/** Forget account `id` locally (token + metadata). Switches active away from it. */
|
|
79
|
+
removeAccount: (id: string) => void;
|
|
80
|
+
/** Re-mint the active identity JWS now (e.g. after forwarding on/off changes the
|
|
81
|
+
* required audience, OB-202). Resolves to the audience the issuer scoped the new
|
|
82
|
+
* token to, or null when unscoped / nothing minted / disconnected. */
|
|
83
|
+
remintIdentity: () => Promise<string | null>;
|
|
44
84
|
}
|
|
45
85
|
/** Cross-window handoff (web): the callback page hands the minted token to the
|
|
46
86
|
* running app over this BroadcastChannel (popup case) or localStorage key
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
2
|
import { type TunnelStatus } from '@book.dev/sdk';
|
|
3
|
+
import { type AudienceNoticeCode } from './forwardingAudience';
|
|
3
4
|
/** Combined provisioning/tunnel status. `idle` = never started this session. */
|
|
4
5
|
export type ForwardingStatus = TunnelStatus | 'idle';
|
|
5
6
|
interface ForwardingContextValue {
|
|
@@ -13,6 +14,24 @@ interface ForwardingContextValue {
|
|
|
13
14
|
host: string | null;
|
|
14
15
|
busy: boolean;
|
|
15
16
|
error: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* A localizable audience-bind/unbind notice (OB-202), shown when the tunnel is up
|
|
19
|
+
* but the audience hardening is incomplete (`partialUnscoped`/`ensureRescope`), a
|
|
20
|
+
* bind step threw (`bindFailed`), or a disable couldn't confirm the relax
|
|
21
|
+
* (`unbindHeld`). The view maps `code` to a `forwarding.*` string; `detail` is the
|
|
22
|
+
* raw error for the `{error}` codes. `null` when there's nothing to show.
|
|
23
|
+
*/
|
|
24
|
+
audienceNotice: {
|
|
25
|
+
code: AudienceNoticeCode;
|
|
26
|
+
detail?: string;
|
|
27
|
+
} | null;
|
|
28
|
+
/**
|
|
29
|
+
* Why a publish was refused before the tunnel opened, for localized + severity-aware
|
|
30
|
+
* display: `unverified` is a precondition (the signed-in owner just needs a verified
|
|
31
|
+
* identity — render it muted, like {@link signInHint}); `claim-failed` is a genuine
|
|
32
|
+
* failure (render it as an error). `null` when there's nothing to show.
|
|
33
|
+
*/
|
|
34
|
+
claimRefusal: 'unverified' | 'claim-failed' | null;
|
|
16
35
|
/** Turn forwarding on: claim the address (sign-in first if needed) + dial out. */
|
|
17
36
|
enable: () => Promise<void>;
|
|
18
37
|
/** Turn forwarding off: drop the tunnel but keep the site key (stable address). */
|
|
@@ -30,6 +30,23 @@ export interface WindowControls {
|
|
|
30
30
|
*/
|
|
31
31
|
watchMaximized?: (cb: (maximized: boolean) => void) => () => void;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Per-account device-token storage, namespaced by a local account id (OB-194).
|
|
35
|
+
* The client can hold several account.book.pub accounts at once (work + personal),
|
|
36
|
+
* so each account's bearer token gets its own slot — no shared key, no
|
|
37
|
+
* cross-account leakage. The desktop backs this with the OS keychain (one entry
|
|
38
|
+
* per account id, like {@link ForwardingPlatform.keyStore}); the web shell — and
|
|
39
|
+
* unsigned desktop *dev* builds, whose per-relink cdhash loses keychain access —
|
|
40
|
+
* leave it undefined and the UI falls back to a namespaced-`localStorage` store.
|
|
41
|
+
*/
|
|
42
|
+
export interface AccountSecretStore {
|
|
43
|
+
/** Read account `id`'s device token, or `null` when none is stored. */
|
|
44
|
+
get(id: string): Promise<string | null>;
|
|
45
|
+
/** Store (overwrite) account `id`'s device token. */
|
|
46
|
+
set(id: string, token: string): Promise<void>;
|
|
47
|
+
/** Forget account `id`'s device token. */
|
|
48
|
+
delete(id: string): Promise<void>;
|
|
49
|
+
}
|
|
33
50
|
/**
|
|
34
51
|
* How the host completes account.book.pub's deep-link sign-in. The desktop sets
|
|
35
52
|
* a custom-scheme `redirectUri` (`openbook://auth-callback`), opens the browser
|
|
@@ -49,6 +66,9 @@ export interface AccountPlatform {
|
|
|
49
66
|
token: string;
|
|
50
67
|
state: string;
|
|
51
68
|
}) => void) => () => void;
|
|
69
|
+
/** Secure, per-account device-token storage (OB-194). Omit on web / desktop dev;
|
|
70
|
+
* the UI then falls back to a namespaced-`localStorage` store. */
|
|
71
|
+
secretStore?: AccountSecretStore;
|
|
52
72
|
}
|
|
53
73
|
/**
|
|
54
74
|
* How the host reads/writes an on-disk book folder (the human-readable
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audience-bind orchestration (OB-202) — the failure-safe choreography that turns
|
|
3
|
+
* forwarding on/off without ever stranding the loopback owner. Exercised entirely
|
|
4
|
+
* through the `setInstancePolicy` / `getInstanceInfo` seams (i.e. `PUT/GET
|
|
5
|
+
* /api/instance`) over a fake instance, NOT by poking the store: these prove the
|
|
6
|
+
* three-phase ORDER, the mid-sequence rollback, the disable cleanup, and the
|
|
7
|
+
* relaunch ensure-scoped short-circuit.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audience-bind orchestration for forwarding (OB-202).
|
|
3
|
+
*
|
|
4
|
+
* Enabling the tunnel binds the instance's identity `audience` to its canonical
|
|
5
|
+
* `<prefix>.book.cloud` host with `requireAudience` (OB-177), so the edge's
|
|
6
|
+
* aud-scoped viewer tokens verify and a token minted for a *different* site is
|
|
7
|
+
* rejected. The catch: the local owner reaches the SAME server over loopback with
|
|
8
|
+
* their OWN token, so requiring the audience while that token is still unscoped
|
|
9
|
+
* would lock the owner out — yet the owner's token can't be scoped to the host
|
|
10
|
+
* until the server already accepts it.
|
|
11
|
+
*
|
|
12
|
+
* This module is the failure-safe choreography that resolves that chicken-and-egg
|
|
13
|
+
* WITHOUT ever stranding the owner. It is pure (all side effects injected) so the
|
|
14
|
+
* three-phase sequence, the mid-sequence rollback, the disable cleanup, and the
|
|
15
|
+
* relaunch short-circuit are all exercised against `setInstancePolicy` /
|
|
16
|
+
* `PUT /api/instance` in tests — never by poking the store directly.
|
|
17
|
+
*/
|
|
18
|
+
import { type InstanceConfig, type InstanceInfo } from '@book.dev/sdk';
|
|
19
|
+
/** Side effects the audience-bind drives, injected so the logic stays testable. */
|
|
20
|
+
export interface AudienceBindDeps {
|
|
21
|
+
/** `PUT /api/instance` — the audience-policy write (gated server-side). */
|
|
22
|
+
setInstancePolicy(patch: Partial<InstanceConfig>): Promise<InstanceConfig>;
|
|
23
|
+
/** `GET /api/instance` — to read the persisted `audience` + `requireAudience`. */
|
|
24
|
+
getInstanceInfo(): Promise<InstanceInfo>;
|
|
25
|
+
/**
|
|
26
|
+
* Re-mint the owner's own identity token, scoped to whatever
|
|
27
|
+
* {@link setLocalAudience} last recorded. Resolves to the audience the issuer
|
|
28
|
+
* ACTUALLY scoped the minted token to — `null` when it returned an unscoped
|
|
29
|
+
* token (the issuer scopes only when it runs an audience allowlist) or when
|
|
30
|
+
* nothing could be minted. The bind decision turns on this REAL value, never on
|
|
31
|
+
* the audience we merely *asked* for.
|
|
32
|
+
*/
|
|
33
|
+
remintIdentity(): Promise<string | null>;
|
|
34
|
+
/** Record (or clear, with `null`) the host the owner's token is scoped to. */
|
|
35
|
+
setLocalAudience(host: string | null): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A stable, localizable code for every non-clean audience outcome (OB-202). The UI
|
|
39
|
+
* maps each to a `forwarding.*` message via `t()`; the English `reason` carried
|
|
40
|
+
* alongside stays for logging and is the `{error}` detail for the failure codes.
|
|
41
|
+
*/
|
|
42
|
+
export type AudienceNoticeCode = 'partialUnscoped' | 'ensureRescope' | 'bindFailed' | 'unbindHeld';
|
|
43
|
+
/** The result of binding (or ensuring) the forwarded audience. */
|
|
44
|
+
export type AudienceBindOutcome =
|
|
45
|
+
/** Phase 3 reached: `requireAudience` is on and the owner's token is host-scoped. */
|
|
46
|
+
{
|
|
47
|
+
status: 'bound';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The `audience` is set but `requireAudience` stays OFF, because no host-scoped
|
|
51
|
+
* owner token exists (the issuer doesn't scope, or the mint failed) — `partialUnscoped`
|
|
52
|
+
* — or a resumed session couldn't re-scope this launch (`ensureRescope`). A token for
|
|
53
|
+
* a *different* site is still rejected; only unscoped tokens stay accepted — no
|
|
54
|
+
* strict isolation, but crucially no loopback lockout.
|
|
55
|
+
*/
|
|
56
|
+
| {
|
|
57
|
+
status: 'partial';
|
|
58
|
+
code: 'partialUnscoped' | 'ensureRescope';
|
|
59
|
+
reason: string;
|
|
60
|
+
}
|
|
61
|
+
/** A phase threw; the binding was relaxed back so loopback stays open. */
|
|
62
|
+
| {
|
|
63
|
+
status: 'failed';
|
|
64
|
+
code: 'bindFailed';
|
|
65
|
+
reason: string;
|
|
66
|
+
};
|
|
67
|
+
/** User-facing reason for the `partial` outcome (issuer returned an unscoped token). */
|
|
68
|
+
export declare const PARTIAL_UNSCOPED_REASON: string;
|
|
69
|
+
/** User-facing reason when a resumed session can't (yet) re-scope the owner token. */
|
|
70
|
+
export declare const ENSURE_RESCOPE_REASON: string;
|
|
71
|
+
/**
|
|
72
|
+
* Bind the forwarded `host` as this instance's required audience via the seamless
|
|
73
|
+
* three-phase switch — and never leave the instance requiring an audience the
|
|
74
|
+
* owner's own token can't satisfy:
|
|
75
|
+
*
|
|
76
|
+
* 1. **accept** the host audience but don't REQUIRE it (the owner's still-unscoped
|
|
77
|
+
* loopback token keeps verifying);
|
|
78
|
+
* 2. **scope** the owner's own token to the host AND confirm the issuer really
|
|
79
|
+
* scoped it — phase 3 proceeds ONLY on a confirmed host-scoped credential;
|
|
80
|
+
* 3. **require** the audience: owner (scoped) + edge tokens carry it, others fail.
|
|
81
|
+
*
|
|
82
|
+
* If phase 2 can't confirm a host-scoped token, hold at `requireAudience:false`
|
|
83
|
+
* (a `partial` outcome) rather than lock the owner out. If any phase throws, relax
|
|
84
|
+
* back to `requireAudience:false` (best-effort) so loopback stays open — the
|
|
85
|
+
* server's loopback-owner recovery still relaxes it even if that PUT is itself
|
|
86
|
+
* rejected.
|
|
87
|
+
*/
|
|
88
|
+
export declare function bindForwardingAudience(host: string, deps: AudienceBindDeps): Promise<AudienceBindOutcome>;
|
|
89
|
+
/**
|
|
90
|
+
* Resume the audience binding on launch WITHOUT relaxing it every time. When the
|
|
91
|
+
* server already persisted `audience==host && requireAudience` (a previous session
|
|
92
|
+
* reached phase 3), only re-scope THIS session's owner token — don't transiently
|
|
93
|
+
* drop `requireAudience` (which would reopen the unscoped-token window on every
|
|
94
|
+
* relaunch). Otherwise (fresh enable, or a prior `partial`), run the full bind.
|
|
95
|
+
*/
|
|
96
|
+
export declare function ensureForwardingAudience(host: string, deps: AudienceBindDeps): Promise<AudienceBindOutcome>;
|
|
97
|
+
/** The result of ensuring the instance is claimed before it is exposed. */
|
|
98
|
+
export type ForwardingClaimOutcome =
|
|
99
|
+
/** We atomically claimed ownership to the enabling account's verified subject. */
|
|
100
|
+
{
|
|
101
|
+
status: 'claimed';
|
|
102
|
+
}
|
|
103
|
+
/** Already claimed (by this owner or anyone) — nothing to do; safe to expose. */
|
|
104
|
+
| {
|
|
105
|
+
status: 'already';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Couldn't claim, so we won't expose. `code` is the stable discriminant the surface
|
|
109
|
+
* localizes + styles by severity: `unverified` is a precondition the signed-in owner
|
|
110
|
+
* clears by verifying their identity (NOT a crash); `claim-failed` is a genuine
|
|
111
|
+
* failure. `reason` is the English fallback for logs / non-UI callers — the UI routes
|
|
112
|
+
* `code` through `t()` (`forwarding.claimRefusedUnverified` / `forwarding.claimFailed`).
|
|
113
|
+
*/
|
|
114
|
+
| {
|
|
115
|
+
status: 'refused';
|
|
116
|
+
code: 'unverified' | 'claim-failed';
|
|
117
|
+
reason: string;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* English fallback when forwarding is refused because the account identity is not
|
|
121
|
+
* JWS-verified yet (on desktop the default owner is `verifiedVia:'local'`). The owner
|
|
122
|
+
* IS already signed in on this path — what's missing is a verified identity, not an
|
|
123
|
+
* account — so this guides verifying, not signing in. UI: `forwarding.claimRefusedUnverified`.
|
|
124
|
+
*/
|
|
125
|
+
export declare const UNVERIFIED_CLAIM_REASON = "To publish, your account identity needs to be verified first.";
|
|
126
|
+
/** English fallback when the claim write did not land. UI: `forwarding.claimFailed`. */
|
|
127
|
+
export declare const CLAIM_FAILED_REASON = "Couldn\u2019t claim this device for your account, so it wasn\u2019t published. Try again.";
|
|
128
|
+
/**
|
|
129
|
+
* Publish-implies-claim (OB-209). Forwarding turns the local instance into a public
|
|
130
|
+
* ingress that BYPASSES the boot exposure backstop (`assertExposureSafe` only guards
|
|
131
|
+
* a listener bind; the tunnel reaches the loopback server). An UNCLAIMED instance
|
|
132
|
+
* short-circuits `authorize()` rule-0 to the legacy guest gate (default
|
|
133
|
+
* `guestAccess:'write'`) — so exposing it unclaimed = anonymous world-write. We
|
|
134
|
+
* therefore CLAIM before we expose: atomically bind ownership to the enabling
|
|
135
|
+
* account's OWN verified subject (the server routes this through the OB-191 CAS and
|
|
136
|
+
* only ever binds the verified principal — never a client-supplied value), and refuse
|
|
137
|
+
* to dial out when there is no verified identity to claim with. Idempotent: a
|
|
138
|
+
* re-enable on an already-claimed instance is a no-op.
|
|
139
|
+
*/
|
|
140
|
+
export declare function ensureClaimedForForwarding(deps: AudienceBindDeps): Promise<ForwardingClaimOutcome>;
|
|
141
|
+
/** The result of unwinding the audience binding on disable. */
|
|
142
|
+
export type AudienceUnbindOutcome =
|
|
143
|
+
/** `requireAudience` was relaxed and the owner token re-minted unscoped. */
|
|
144
|
+
{
|
|
145
|
+
status: 'relaxed';
|
|
146
|
+
}
|
|
147
|
+
/** The relax was NOT confirmed; scoping was LEFT INTACT to avoid a lockout. */
|
|
148
|
+
| {
|
|
149
|
+
status: 'held';
|
|
150
|
+
code: 'unbindHeld';
|
|
151
|
+
reason: string;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Unwind the binding on disable in the SAFE order: relax `requireAudience` FIRST —
|
|
155
|
+
* while the owner's token is still scoped to the host, so the PUT verifies — and
|
|
156
|
+
* ONLY drop the local scoping + re-mint an unscoped owner token once the relax is
|
|
157
|
+
* CONFIRMED. If the relax fails (the owner is already audience-locked, or the
|
|
158
|
+
* server is unreachable), DON'T unscope: that would strand the owner behind a
|
|
159
|
+
* requirement their token no longer satisfies — a permanent loopback lockout.
|
|
160
|
+
* Leave the scoping intact (`held`) so the owner stays verified and can retry.
|
|
161
|
+
*
|
|
162
|
+
* The instance keeps its `audience` for address stability — a later re-enable just
|
|
163
|
+
* re-asserts `requireAudience`.
|
|
164
|
+
*/
|
|
165
|
+
export declare function unbindForwardingAudience(deps: AudienceBindDeps): Promise<AudienceUnbindOutcome>;
|
|
@@ -36,11 +36,22 @@ export interface PageDocumentProps {
|
|
|
36
36
|
* under the header instead of being pushed down by a big gap. */
|
|
37
37
|
hasDatabase?: boolean;
|
|
38
38
|
}
|
|
39
|
+
/** Imperative handle the host uses to hand the caret back from the editor. */
|
|
40
|
+
export interface PageTitleHandle {
|
|
41
|
+
/** Focus the title and place the caret at the end (editor → title hand-off). */
|
|
42
|
+
focusEnd(): void;
|
|
43
|
+
}
|
|
39
44
|
export declare const PageHeader: React.FC<{
|
|
40
45
|
title: string;
|
|
41
46
|
icon: string;
|
|
42
47
|
pageId?: string;
|
|
48
|
+
/** Read-only (a viewer who can't write the page): the title + icon lock down. */
|
|
49
|
+
readOnly?: boolean;
|
|
43
50
|
onTitleChange?: (title: string) => void;
|
|
44
51
|
onIconChange?: (emoji: string) => void;
|
|
45
52
|
onTitleActiveChange?: (active: boolean) => void;
|
|
53
|
+
/** Hand the caret down to the editor (Enter, or ↓ on the title's last line). */
|
|
54
|
+
onLeaveToEditor?: () => void;
|
|
55
|
+
/** Lets the host (BlockPageDocument) drive focus back here from the editor. */
|
|
56
|
+
focusRef?: React.Ref<PageTitleHandle>;
|
|
46
57
|
}>;
|