@fias/create-fias-plugin 1.11.2 → 1.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fias/create-fias-plugin",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,4 +1,4 @@
1
- <!-- fias-sdk-guide-version: 2.19.0 -->
1
+ <!-- fias-sdk-guide-version: 2.20.0 -->
2
2
 
3
3
  # FIAS Plugin Development Guide
4
4
 
@@ -772,30 +772,26 @@ const { referenceId } = await vault.attach(documentId, {
772
772
  await vault.detach(documentId, referenceId);
773
773
  ```
774
774
 
775
- **Saving into the user's OWN files** — `upload(bytes, { ..., userVisible: true })`. By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
775
+ **Saving into the user's Documents** — `upload(bytes, { ..., destination: 'documents' })` (`userVisible: true` is the same thing). By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
776
776
 
777
777
  ```tsx
778
778
  const saved = await vault.upload(bytes, {
779
- name: 'sunset.png',
779
+ name: 'sunset.png', // a suggestion — the user can change it
780
780
  mimeType: 'image/png',
781
- userVisible: true,
781
+ destination: 'documents',
782
782
  });
783
+ // saved.documentId, saved.name (what the user kept), saved.access ('none' | 'read' | 'write')
783
784
  ```
784
785
 
785
- Requires the `vault:user-documents:write` permission, and the HOST asks the user to confirm each save by name — you cannot script it, batch it, or pre-approve it. A decline rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. The file lands in `/My-Fias/<your arche's folder>/`, which the platform assigns — you cannot choose it, and `folderPath` is rejected alongside `userVisible`. Your arche must be registered for a user-visible folder platform-side (`ARCHE_NOT_REGISTERED_FOR_USER_VAULT` if not). Passing `replacesDocumentId` saves a new version in place; a version keeps its predecessor's visibility, so you cannot flip a user's visible file to private or vice versa (`VISIBILITY_MISMATCH`). Not available in preview or the dev harness — test it in the published plugin.
786
+ The host opens its **Save dialog**: the user picks a folder in their Documents, can rename the file, and settles a name clash (keep both, or replace a file of the same type). You cannot script it, batch it, or pre-approve it, and you never learn the folder. Requires `vault:user-documents:write`. Cancelling rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. `folderPath`, `replacesDocumentId`, `tags` and `sensitivity` are rejected alongside it (the user decides those). Not available in preview or the dev harness — test it in the published plugin.
786
787
 
787
- **Handing over a file you already made** — `promoteToUserFiles(documentId)`. If the artifact already exists as one of your private documents, do NOT re-upload it with `userVisible` — that creates a second copy, doubles the user's storage, and orphans anything pointing at the original. Promote it instead: the same document moves into the user's files, keeping its `documentId`.
788
+ **Afterwards the file is the user's.** `useVaultDocuments()` no longer lists it. What you keep is reported as `access`: with `vault:user-documents:read` you can open it again (`'read'`) through `useVaultUserDocuments()`, and with `vault:user-documents:edit` the dialog offers the user "keep editing" (`'write'`) so you can keep saving it with `useVaultUserDocuments().saveContent`. The user can change either in Arche Access.
788
789
 
789
- ```typescript
790
- try {
791
- await vault.promoteToUserFiles(documentId); // the HOST asks the user; you cannot script it
792
- } catch (err) {
793
- if (err instanceof FiasBridgeError && err.code === 'SAVE_DECLINED') return; // a normal outcome
794
- throw err;
795
- }
796
- ```
790
+ **Arche Saves, automatically** — `upload(bytes, { ..., destination: 'arche-saves' })` saves into the user's `Arche Saves/<your label>/` with no dialog. Only for an arche a Fias admin has designated (that issues its label, which is yours for good) and that declares `vault:arche-saves:write` (human-reviewed). Otherwise it rejects with `ARCHE_NOT_DESIGNATED` or `PERMISSION_DENIED`. The files are the user's to keep, move or delete; you can keep reading them while they stay in Arche Saves.
797
791
 
798
- Same requirements as a user-visible upload (`vault:user-documents:write`, a registered folder — the platform picks the destination). It is **one-way and it changes what you can do**: once the file is the user's, this hook's `saveContent`, `update` and `delete` reject, and there is no way to take a file back out of the user's files. To keep saving it, declare `vault:user-documents:edit` — the host's confirmation then offers the user "let this app keep editing", the result carries `editAccess`, and when it is `true` you carry on with `useVaultUserDocuments().saveContent` (below). Without it, promote when the user is DONE — never while your editor still needs to save. After a `SAVE_DECLINED`, re-`list()` before assuming nothing moved: the host performs the move itself, and if its page is torn down mid-move you are told "declined" for a promote that completed — the document's `visibility` is the truth. Re-asking is capped at 5 a minute, like the picker. Not available in preview or the dev harness.
792
+ **`saveCopy(documentId, { suggestedName? })`** — hand the user a copy of one of your own documents through the same dialog, without re-sending the bytes (needs `vault:documents:read` too). Your working file is unchanged; delete it afterwards if you no longer need it.
793
+
794
+ **A document's visibility never changes.** Your private documents stay private, and a file in the user's files stays theirs — there is no call that moves one into the other.
799
795
 
800
796
  **Sensitivity tiers** (`'standard'` | `'confidential'` | `'proprietary'`) gate downstream read access per the Vault's standard matrix. Defaults to `'standard'`.
801
797
 
@@ -834,6 +830,7 @@ const { bytes } = await userDocs.getBytes(id); // raw bytes for PROCESSING binar
834
830
  - Grants are **per-document** and **standing**: the plugin can re-read granted documents in later sessions until the user removes access in **Vault → App Access**.
835
831
  - After revocation, `get`/`getDownloadUrl` reject with `DOCUMENT_NOT_FOUND` — handle that path gracefully (drop the document from your UI; do not retry in a loop).
836
832
  - `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
833
+ - **Say what you can open:** `pick({ accept: ['application/pdf', 'image/*', '.docx'] })` takes MIME types, MIME families or extensions, up to 20. The host's Open dialog offers the matching files; the user can still reveal the rest, shown disabled. A malformed list rejects with `INVALID_PARAMS`. Omitted, every type is offered.
837
834
  - Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
838
835
  - `getDownloadUrl` is for DISPLAY (`<img src>`); your iframe cannot fetch the URL (CSP). To process binary content (parse a PDF, transform an image), use `getBytes(id)` — size-capped at 15 MB (`CONSENTED_DOCUMENT_TOO_LARGE` names the cap; ask the user to open the file manually instead). `getBytes` has a tighter per-minute budget than the rest of the family because it can move megabytes: read sequentially, and surface a rate-limit error rather than retrying in a loop.
839
836
  - Revocation behaves differently per method: `getBytes` and `get` re-check the grant on every call, so they start failing immediately. A URL already handed out by `getDownloadUrl` keeps working until it expires (1 hour) — that is the documented contract, not a bug.
@@ -1050,6 +1047,32 @@ const fresh = await getUrl(assetId);
1050
1047
 
1051
1048
  Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
1052
1049
 
1050
+ ### `useFiasContent()` — This plugin's content pack
1051
+
1052
+ **Permission:** `entities:client_invoke`
1053
+ **Returns:** `FiasContentApi`
1054
+
1055
+ Ship large or numerous files — lessons, articles, data, images, audio, fonts — in a `content/` directory beside `src/`. They are not bundled: `fias-dev submit` uploads them separately (only the files that changed), the review reads every one, and the published plugin reads them at runtime through this hook. The host page fetches, verifies and caches them; your plugin never touches the network.
1056
+
1057
+ ```tsx
1058
+ import { useFiasContent, FiasContentError } from '@fias/arche-sdk';
1059
+
1060
+ const content = useFiasContent();
1061
+
1062
+ const lessons = await content.list('lessons/'); // [{ path, sizeBytes, mimeType }]
1063
+ const markdown = await content.getText('lessons/01-intro.md');
1064
+ const course = await content.getJson<Course>('course.json');
1065
+ const batch = await content.getMany(['a.md', 'b.md'], 'text'); // up to 100 files per call
1066
+ const cover = await content.getObjectUrl('img/cover.webp'); // blob: URL for <img src>
1067
+ const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long streaming audio
1068
+ ```
1069
+
1070
+ - Paths are relative to `content/`. Formats: `.md`, `.txt`, `.json` (text, ≤ 5 MB each); `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif` (static, single-frame images, ≤ 20 MB and 50 megapixels); `.ogg` (audio, ≤ 50 MB); `.woff2` (fonts, ≤ 5 MB). A pack holds at most 10,000 files, 2,000 images, 20 MB of text and 500 MB in total. Run `npx fias-dev content check` to validate without submitting.
1071
+ - Prefer `getObjectUrl` for images and fonts: repeat views come from the host cache. `getUrl` is for media that must stream or seek; it bypasses the cache, and a URL held past its `expiresAt` must be requested again.
1072
+ - Failures throw `FiasContentError` with a `code`: `CONTENT_NOT_FOUND`, `CONTENT_NOT_TEXT`, `CONTENT_TOO_LARGE`, `CONTENT_UNAVAILABLE`, `CONTENT_THROTTLED`, `CONTENT_UNAVAILABLE_IN_PREVIEW`, `PERMISSION_DENIED`, `RATE_LIMIT`. `getMany` returns the error for a failed path instead of throwing.
1073
+ - There is no published pack in the Arche Builder preview, so calls there fail with `CONTENT_UNAVAILABLE_IN_PREVIEW`; the dev harness serves your local `content/` directory.
1074
+ - Every publish reviews all of the content again. Reads are free for your users; the pack's storage and bandwidth are billed to you, the contributor.
1075
+
1053
1076
  ### `useCommunityAssets()` — User-published images, visible to this arche's users
1054
1077
 
1055
1078
  **Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
@@ -1159,18 +1182,29 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
1159
1182
  openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
1160
1183
  ```
1161
1184
 
1162
- **Receiving a deep link.** There is nothing extra to opt into: `currentPath` in
1163
- the init payload is the full platform path the host loaded you at, so an arche
1164
- that wants to be a deep-link target just reads it once at boot.
1185
+ **Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
1186
+ the host's path expressed in YOUR arche's route space — the same space
1187
+ `navigateTo` accepts — so an arche that wants to be a deep-link target just
1188
+ reads it at boot. The `/a/<arche>` prefix is stripped for you, and your home
1189
+ screen reads `/`.
1165
1190
 
1166
1191
  ```tsx
1167
1192
  const { currentPath } = useFiasNavigation();
1168
- // e.g. '/a/arc_0123…/map' → 'map'
1169
- const page = currentPath.split('/').slice(3).join('/') || 'home';
1193
+ // a deep link to '/a/arc_0123…/map' arrives here as '/map'
1194
+ const page = currentPath.replace(/^\//, '') || 'home';
1170
1195
  ```
1171
1196
 
1172
- It is set once, when your app mounts. A deep link from another arche always
1173
- mounts you fresh, so that is exactly when you need it.
1197
+ It is `''` until the host's `init` message lands — the bridge handshake is
1198
+ async, so your first render usually beats it. Treat `''` as "not known yet"
1199
+ rather than as your home screen; that distinction is what keeps a direct entry
1200
+ such as a share link from being lost to the race.
1201
+
1202
+ After that it TRACKS the host: it updates when you call `navigateTo`, and when
1203
+ the user moves the host themselves with browser back/forward. So a router
1204
+ driven off `currentPath` stays in step with the address bar, and the back
1205
+ button works the way your users expect — you do not have to mirror the path in
1206
+ your own state. The dev harness echoes navigations the same way, so what you
1207
+ see locally is what ships.
1174
1208
 
1175
1209
  ### Opening external links
1176
1210
 
@@ -1,4 +1,4 @@
1
- <!-- fias-sdk-guide-version: 2.19.0 -->
1
+ <!-- fias-sdk-guide-version: 2.20.0 -->
2
2
 
3
3
  # FIAS Plugin Development Guide
4
4
 
@@ -772,30 +772,26 @@ const { referenceId } = await vault.attach(documentId, {
772
772
  await vault.detach(documentId, referenceId);
773
773
  ```
774
774
 
775
- **Saving into the user's OWN files** — `upload(bytes, { ..., userVisible: true })`. By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
775
+ **Saving into the user's Documents** — `upload(bytes, { ..., destination: 'documents' })` (`userVisible: true` is the same thing). By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
776
776
 
777
777
  ```tsx
778
778
  const saved = await vault.upload(bytes, {
779
- name: 'sunset.png',
779
+ name: 'sunset.png', // a suggestion — the user can change it
780
780
  mimeType: 'image/png',
781
- userVisible: true,
781
+ destination: 'documents',
782
782
  });
783
+ // saved.documentId, saved.name (what the user kept), saved.access ('none' | 'read' | 'write')
783
784
  ```
784
785
 
785
- Requires the `vault:user-documents:write` permission, and the HOST asks the user to confirm each save by name — you cannot script it, batch it, or pre-approve it. A decline rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. The file lands in `/My-Fias/<your arche's folder>/`, which the platform assigns — you cannot choose it, and `folderPath` is rejected alongside `userVisible`. Your arche must be registered for a user-visible folder platform-side (`ARCHE_NOT_REGISTERED_FOR_USER_VAULT` if not). Passing `replacesDocumentId` saves a new version in place; a version keeps its predecessor's visibility, so you cannot flip a user's visible file to private or vice versa (`VISIBILITY_MISMATCH`). Not available in preview or the dev harness — test it in the published plugin.
786
+ The host opens its **Save dialog**: the user picks a folder in their Documents, can rename the file, and settles a name clash (keep both, or replace a file of the same type). You cannot script it, batch it, or pre-approve it, and you never learn the folder. Requires `vault:user-documents:write`. Cancelling rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. `folderPath`, `replacesDocumentId`, `tags` and `sensitivity` are rejected alongside it (the user decides those). Not available in preview or the dev harness — test it in the published plugin.
786
787
 
787
- **Handing over a file you already made** — `promoteToUserFiles(documentId)`. If the artifact already exists as one of your private documents, do NOT re-upload it with `userVisible` — that creates a second copy, doubles the user's storage, and orphans anything pointing at the original. Promote it instead: the same document moves into the user's files, keeping its `documentId`.
788
+ **Afterwards the file is the user's.** `useVaultDocuments()` no longer lists it. What you keep is reported as `access`: with `vault:user-documents:read` you can open it again (`'read'`) through `useVaultUserDocuments()`, and with `vault:user-documents:edit` the dialog offers the user "keep editing" (`'write'`) so you can keep saving it with `useVaultUserDocuments().saveContent`. The user can change either in Arche Access.
788
789
 
789
- ```typescript
790
- try {
791
- await vault.promoteToUserFiles(documentId); // the HOST asks the user; you cannot script it
792
- } catch (err) {
793
- if (err instanceof FiasBridgeError && err.code === 'SAVE_DECLINED') return; // a normal outcome
794
- throw err;
795
- }
796
- ```
790
+ **Arche Saves, automatically** — `upload(bytes, { ..., destination: 'arche-saves' })` saves into the user's `Arche Saves/<your label>/` with no dialog. Only for an arche a Fias admin has designated (that issues its label, which is yours for good) and that declares `vault:arche-saves:write` (human-reviewed). Otherwise it rejects with `ARCHE_NOT_DESIGNATED` or `PERMISSION_DENIED`. The files are the user's to keep, move or delete; you can keep reading them while they stay in Arche Saves.
797
791
 
798
- Same requirements as a user-visible upload (`vault:user-documents:write`, a registered folder — the platform picks the destination). It is **one-way and it changes what you can do**: once the file is the user's, this hook's `saveContent`, `update` and `delete` reject, and there is no way to take a file back out of the user's files. To keep saving it, declare `vault:user-documents:edit` — the host's confirmation then offers the user "let this app keep editing", the result carries `editAccess`, and when it is `true` you carry on with `useVaultUserDocuments().saveContent` (below). Without it, promote when the user is DONE — never while your editor still needs to save. After a `SAVE_DECLINED`, re-`list()` before assuming nothing moved: the host performs the move itself, and if its page is torn down mid-move you are told "declined" for a promote that completed — the document's `visibility` is the truth. Re-asking is capped at 5 a minute, like the picker. Not available in preview or the dev harness.
792
+ **`saveCopy(documentId, { suggestedName? })`** — hand the user a copy of one of your own documents through the same dialog, without re-sending the bytes (needs `vault:documents:read` too). Your working file is unchanged; delete it afterwards if you no longer need it.
793
+
794
+ **A document's visibility never changes.** Your private documents stay private, and a file in the user's files stays theirs — there is no call that moves one into the other.
799
795
 
800
796
  **Sensitivity tiers** (`'standard'` | `'confidential'` | `'proprietary'`) gate downstream read access per the Vault's standard matrix. Defaults to `'standard'`.
801
797
 
@@ -834,6 +830,7 @@ const { bytes } = await userDocs.getBytes(id); // raw bytes for PROCESSING binar
834
830
  - Grants are **per-document** and **standing**: the plugin can re-read granted documents in later sessions until the user removes access in **Vault → App Access**.
835
831
  - After revocation, `get`/`getDownloadUrl` reject with `DOCUMENT_NOT_FOUND` — handle that path gracefully (drop the document from your UI; do not retry in a loop).
836
832
  - `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
833
+ - **Say what you can open:** `pick({ accept: ['application/pdf', 'image/*', '.docx'] })` takes MIME types, MIME families or extensions, up to 20. The host's Open dialog offers the matching files; the user can still reveal the rest, shown disabled. A malformed list rejects with `INVALID_PARAMS`. Omitted, every type is offered.
837
834
  - Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
838
835
  - `getDownloadUrl` is for DISPLAY (`<img src>`); your iframe cannot fetch the URL (CSP). To process binary content (parse a PDF, transform an image), use `getBytes(id)` — size-capped at 15 MB (`CONSENTED_DOCUMENT_TOO_LARGE` names the cap; ask the user to open the file manually instead). `getBytes` has a tighter per-minute budget than the rest of the family because it can move megabytes: read sequentially, and surface a rate-limit error rather than retrying in a loop.
839
836
  - Revocation behaves differently per method: `getBytes` and `get` re-check the grant on every call, so they start failing immediately. A URL already handed out by `getDownloadUrl` keeps working until it expires (1 hour) — that is the documented contract, not a bug.
@@ -1050,6 +1047,32 @@ const fresh = await getUrl(assetId);
1050
1047
 
1051
1048
  Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
1052
1049
 
1050
+ ### `useFiasContent()` — This plugin's content pack
1051
+
1052
+ **Permission:** `entities:client_invoke`
1053
+ **Returns:** `FiasContentApi`
1054
+
1055
+ Ship large or numerous files — lessons, articles, data, images, audio, fonts — in a `content/` directory beside `src/`. They are not bundled: `fias-dev submit` uploads them separately (only the files that changed), the review reads every one, and the published plugin reads them at runtime through this hook. The host page fetches, verifies and caches them; your plugin never touches the network.
1056
+
1057
+ ```tsx
1058
+ import { useFiasContent, FiasContentError } from '@fias/arche-sdk';
1059
+
1060
+ const content = useFiasContent();
1061
+
1062
+ const lessons = await content.list('lessons/'); // [{ path, sizeBytes, mimeType }]
1063
+ const markdown = await content.getText('lessons/01-intro.md');
1064
+ const course = await content.getJson<Course>('course.json');
1065
+ const batch = await content.getMany(['a.md', 'b.md'], 'text'); // up to 100 files per call
1066
+ const cover = await content.getObjectUrl('img/cover.webp'); // blob: URL for <img src>
1067
+ const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long streaming audio
1068
+ ```
1069
+
1070
+ - Paths are relative to `content/`. Formats: `.md`, `.txt`, `.json` (text, ≤ 5 MB each); `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif` (static, single-frame images, ≤ 20 MB and 50 megapixels); `.ogg` (audio, ≤ 50 MB); `.woff2` (fonts, ≤ 5 MB). A pack holds at most 10,000 files, 2,000 images, 20 MB of text and 500 MB in total. Run `npx fias-dev content check` to validate without submitting.
1071
+ - Prefer `getObjectUrl` for images and fonts: repeat views come from the host cache. `getUrl` is for media that must stream or seek; it bypasses the cache, and a URL held past its `expiresAt` must be requested again.
1072
+ - Failures throw `FiasContentError` with a `code`: `CONTENT_NOT_FOUND`, `CONTENT_NOT_TEXT`, `CONTENT_TOO_LARGE`, `CONTENT_UNAVAILABLE`, `CONTENT_THROTTLED`, `CONTENT_UNAVAILABLE_IN_PREVIEW`, `PERMISSION_DENIED`, `RATE_LIMIT`. `getMany` returns the error for a failed path instead of throwing.
1073
+ - There is no published pack in the Arche Builder preview, so calls there fail with `CONTENT_UNAVAILABLE_IN_PREVIEW`; the dev harness serves your local `content/` directory.
1074
+ - Every publish reviews all of the content again. Reads are free for your users; the pack's storage and bandwidth are billed to you, the contributor.
1075
+
1053
1076
  ### `useCommunityAssets()` — User-published images, visible to this arche's users
1054
1077
 
1055
1078
  **Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
@@ -1159,18 +1182,29 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
1159
1182
  openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
1160
1183
  ```
1161
1184
 
1162
- **Receiving a deep link.** There is nothing extra to opt into: `currentPath` in
1163
- the init payload is the full platform path the host loaded you at, so an arche
1164
- that wants to be a deep-link target just reads it once at boot.
1185
+ **Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
1186
+ the host's path expressed in YOUR arche's route space — the same space
1187
+ `navigateTo` accepts — so an arche that wants to be a deep-link target just
1188
+ reads it at boot. The `/a/<arche>` prefix is stripped for you, and your home
1189
+ screen reads `/`.
1165
1190
 
1166
1191
  ```tsx
1167
1192
  const { currentPath } = useFiasNavigation();
1168
- // e.g. '/a/arc_0123…/map' → 'map'
1169
- const page = currentPath.split('/').slice(3).join('/') || 'home';
1193
+ // a deep link to '/a/arc_0123…/map' arrives here as '/map'
1194
+ const page = currentPath.replace(/^\//, '') || 'home';
1170
1195
  ```
1171
1196
 
1172
- It is set once, when your app mounts. A deep link from another arche always
1173
- mounts you fresh, so that is exactly when you need it.
1197
+ It is `''` until the host's `init` message lands — the bridge handshake is
1198
+ async, so your first render usually beats it. Treat `''` as "not known yet"
1199
+ rather than as your home screen; that distinction is what keeps a direct entry
1200
+ such as a share link from being lost to the race.
1201
+
1202
+ After that it TRACKS the host: it updates when you call `navigateTo`, and when
1203
+ the user moves the host themselves with browser back/forward. So a router
1204
+ driven off `currentPath` stays in step with the address bar, and the back
1205
+ button works the way your users expect — you do not have to mirror the path in
1206
+ your own state. The dev harness echoes navigations the same way, so what you
1207
+ see locally is what ships.
1174
1208
 
1175
1209
  ### Opening external links
1176
1210