Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
55e6584
[sqlite] Build the devtools plugin UI from the pnpm workspace (#48542)
Kudo Aug 18, 2026
a0f5d9a
[sqlite] Remove libSQL support (#48543)
Kudo Aug 18, 2026
5b5a705
[font][Android] Support variation axes in the config plugin (#48621)
vonovak Aug 18, 2026
63f227f
[docs] Fix multiple markdown export issues (#49041)
amandeepmittal Aug 18, 2026
af32229
[docs] Update additional resources (#49043)
amandeepmittal Aug 18, 2026
000ac5a
[docs] Guard null query results in the sqlite examples (#49057)
amandeepmittal Aug 18, 2026
1ae6855
[ios][autolinking] Keep React Native's post-install xcconfig edits (#…
Kudo Aug 18, 2026
a943740
[docs] Document Observe dashboard toggle (#49059)
kadikraman Aug 18, 2026
827058c
[sqlite] Fix leaking devtools client in dev-plugin-webui tests (#49060)
Kudo Aug 18, 2026
c28eca9
[ios][file-system] Fix wrong permissions for text() and bytes() (#42422)
simoneldevig Aug 18, 2026
7e658dd
[docs] Fix type errors in host, notifications, age-range, and svg exa…
amandeepmittal Aug 18, 2026
7dc8002
[docs] Add screenshots for multiple component examples in Expo UI (#4…
amandeepmittal Aug 18, 2026
fb9fc79
[fingerprint] Hash the font file an expo-font family declares once (#…
vonovak Aug 18, 2026
c433b9a
[cli] Pin agent-cli-detector to 0.1.6 (#49033)
davidmokos Aug 18, 2026
c4d0531
[github] verify: record the option set behind a fix-mode pull request…
Kudo Aug 18, 2026
0b55cbb
[sqlite] Update SQLite to 3.53.3 and SQLCipher to 4.17.0 (#48544)
Kudo Aug 18, 2026
dabdcb1
[docs] show a variable font in the expo-font config plugin example (#…
vonovak Aug 18, 2026
80e2888
[android][notifications] Fix fatal import in Expo Go from token liste…
alanjhughes Aug 18, 2026
2c8f48c
[expo-ui] feat: add dismissOnBackPress to universal BottomSheet on An…
webdevsamran Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion .github/verify/critic.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Read the draft once for its argument, then go looking for the gap between what i
16. **Automation boundary.** In work mode, requests touching the fixed publisher's denylist or exceeding 20 files / 600 lines should be declined for a human. The agent must not disguise a larger design decision as a small mechanical task.
17. **Changelog and commit-message conventions.** A `CHANGELOG.md` entry must match `guides/contributing/Updating Changelogs.md`: one line, with at most one trailing link group — the pull request and the author. An issue link in an entry, or two link groups, is MUST-FIX even when a neighboring entry has the same defect (earlier bot entries spread it). In verify mode the entry ends with no link group at all, because the pull-request number does not exist yet. In work mode, grade `pr.md` for size: past its first line it is a commit body and must be at most one short paragraph — verification narrative, command output, or file lists there are SHOULD-FIX, they belong in `findings.md`.
18. **Ignored duplicates.** `.verify-context/related.json` lists issues and pull requests whose text matches the target. If it names an open pull request that plausibly fixes the same bug and the draft proposes a fix without addressing it, that is MUST-FIX: the draft must either establish the difference or withdraw the proposal in favor of the existing pull request. A plausible duplicate issue the draft never mentions is SHOULD-FIX.
19. **Prose that breaks Simplified Technical English.** The draft's prose must follow the ASD-STE100 rules its prompt states: one term per thing, sentences of 20 words or fewer, active voice, plain words, no idiom or hedge. Grade violations SHOULD-FIX — they cost non-native readers time, and the agent can rewrite without new evidence. Quoted code, commands and their output, error strings, identifiers, and file paths are verbatim and exempt; do not flag them.
19. **A decorative option set.** A fix-mode `pr.md` should carry a marker-wrapped "Options considered" block. Treat its entries as claims, like any other. Attack a straw option nobody would pick, written to make the chosen one look inevitable. Attack options that address different failure mechanisms; that means the cause is not established. Attack a cost that no one can check ("less clean", "more complex"). Some drafts present a non-chosen option as built or tested. Grade that MUST-FIX when the log shows only the chosen option was built. The reverse failure counts equally. A single-option set with a stated reason is a valid record. Do not demand alternatives for a change with no design space; that demand is what produces straw. A missing block on a fix-mode draft is SHOULD-FIX, because the agent can add the honest record without new evidence.
20. **Prose that breaks Simplified Technical English.** The draft's prose must follow the ASD-STE100 rules its prompt states: one term per thing, sentences of 20 words or fewer, active voice, plain words, no idiom or hedge. Grade violations SHOULD-FIX — they cost non-native readers time, and the agent can rewrite without new evidence. Quoted code, commands and their output, error strings, identifiers, and file paths are verbatim and exempt; do not flag them.

Items about reproduction arms, device environments, defaults, and reporter complaints apply to work mode only when the requested task makes those claims. Do not demand simulator evidence from a documentation backport; that would violate the proportionality rule you are reviewing.

Expand Down
25 changes: 25 additions & 0 deletions .github/verify/prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ Two shapes deserve their own treatment:

5. **Write `.verify-out/pr.md`.** First line is the pull-request title (imperative, specific — "Fix `use_dev_client` detection in EXUpdates.podspec", not "Fix bug"). The rest is the body: what changed, why that is the cause, and how it was verified, citing your evidence. The server prepends a banner marking the pull request agent-authored and unreviewed, and links the run — do not write your own disclaimer.

**Record the options you weighed**, in a marker-wrapped section at the end of the body:

```
<!-- expo-bot:fix-options v1 -->
<details><summary>Options considered</summary>

1. **<the approach, one sentence.>** <its cost, in a checkable term>. Rejected: <why>.
2. **<the approach, one sentence.>** Chosen: <why it won>.

</details>
<!-- /expo-bot:fix-options -->
```

This is a record, not a new phase. You already form and discard approaches while you establish the cause. Write down the ones you actually considered:

- **Never invent an option.** A filler entry is worse than a short list.
- **One option is a valid answer.** A missing null check has no design space. Write the single entry, then one line that says why no alternative existed.
- **Every option must address the failure mechanism you named.** Two options that fix different problems mean the cause is not established.
- **Every cost must be checkable.** Name the files touched, the behaviour that changes for apps that work today, a check the option would fail, or a platform it would not cover. "Less clean" and "more complex" are not costs.
- **Treat "do nothing" as a real candidate** whenever the behaviour may be intentional. Write it as "Do nothing and document the behaviour".
- **Mark exactly ONE option chosen.** Write the literal token `Chosen:` on that entry, and on no other line of the block. A later command counts that token to find the live option. The ordinary word "chosen" elsewhere in the prose is fine; a second `Chosen:` is not.
- **Only the chosen option is built and verified.** Never describe an option you did not build as tested.
- **At most FOUR options.** If the honest set needs more, the change is a design decision, and those belong to a human (condition 3).
- **Keep the markers, the `<details>` wrapper, and the numbering exactly as shown.** Write both markers, once each, start before end. The list collapses behind the `<summary>` triangle so the pull-request body stays scannable — the same shape the findings comment uses. Leave the BLANK LINE after the `<summary>` line and before `</details>`, or the list inside will not render as markdown. A maintainer command addresses an option by its number, so the numbers are an interface, not decoration.

**Do not hard-wrap the prose.** This is a file, so the instinct is to format it like source and break lines at 80 or 90 columns. GitHub renders a pull-request body as GitHub-Flavored Markdown, where a single newline is a VISIBLE line break — a wrapped paragraph arrives as a column of ragged short lines. Write each paragraph as ONE line, however long it runs, and let the browser wrap it. Blank lines still separate paragraphs; code fences, tables and list items keep their own line structure.

**Withdrawing is a first-class outcome.** If you tried a change and rejected it — it regressed something, you could not verify it, you decided it belongs to a human — make the FIRST LINE of `pr.md` say so: `No pull request should be created from this run.` The push step reads that line and opens nothing, and posts your reason to the thread instead. Note that you have no shell, so you cannot DELETE a file you created; emptying it is enough, because an addition with no lines in it no longer counts as a change. Say what you withdrew and why in the lines below, since that is what gets published.
Expand Down
40 changes: 39 additions & 1 deletion .github/workflows/agent-commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,40 @@ jobs:
related_prs_md=$(jq -r '.[0:5][] | "- \(.url) (\(.title)) — shared files: \(.shared | join(", "))"' "$RUNNER_TEMP/pr-overlaps.json")
fi
fi
# The prompt asks fix mode for a marker-wrapped "Options considered"
# block, and the markers are a parsing interface, not decoration. So
# the published body must carry either a well-formed block or no
# markers at all: one unmatched or duplicated marker leaves the block
# open, and a reader that scans by marker then treats the rest of the
# body as options.
#
# Never reject over it. Throwing away a verified fix at the last step
# over a missing paragraph is the worse outcome, and an honest gap is
# itself information for the reviewer. A malformed block is stripped
# back to marker-free text, and the notice says which case happened.
desc="$RUNNER_TEMP/pr-desc.md"
tail -n +2 .verify-out/pr.md 2>/dev/null > "$desc" || true
[ -s "$desc" ] || echo "_No description was provided by the run._" > "$desc"
opt_start='<!-- expo-bot:fix-options v1 -->'
opt_end='<!-- /expo-bot:fix-options -->'
opt_note=""
sc=$(grep -cF "$opt_start" "$desc" || true)
ec=$(grep -cF "$opt_end" "$desc" || true)
s_line=$(grep -nF "$opt_start" "$desc" 2>/dev/null | head -n1 | cut -d: -f1 || true)
e_line=$(grep -nF "$opt_end" "$desc" 2>/dev/null | head -n1 | cut -d: -f1 || true)
if [ "$sc" -ne 1 ] || [ "$ec" -ne 1 ] || [ "${s_line:-0}" -ge "${e_line:-0}" ]; then
# Match the marker NAME, not the exact strings: a mistyped or
# re-spaced marker must be removed too, or it survives as a
# half-open block for the next reader.
stray=$(grep -c 'expo-bot:fix-options' "$desc" || true)
grep -v 'expo-bot:fix-options' "$desc" > "$RUNNER_TEMP/pr-desc-clean.md" || true
mv "$RUNNER_TEMP/pr-desc-clean.md" "$desc"
if [ "$stray" -eq 0 ]; then
opt_note="_This run recorded no option set: the fix was published without a record of the alternatives considered._"
else
opt_note="_This run wrote a malformed \`Options considered\` block: $sc start marker(s), $ec end marker(s), $stray marker line(s) in total. The markers were removed so nothing can misread the body. The fix itself is unaffected._"
fi
fi
{
# ONE LINE PER PARAGRAPH, however long. GitHub renders a newline
# inside a comment or pull-request body as a hard <br>, unlike a
Expand All @@ -1922,13 +1956,17 @@ jobs:
echo ""
echo "Requested by @$TRIGGER_ACTOR · [$run_name]($RUN_URL) · refs #$ISSUE_NUMBER"
echo ""
tail -n +2 .verify-out/pr.md 2>/dev/null || echo "_No description was provided by the run._"
cat "$desc"
if [ -n "$related_prs_md" ]; then
echo ""
echo "**Open pull requests touching the same files** (from the publish-time duplicate check — confirm they are distinct before merging both):"
echo ""
printf '%s\n' "$related_prs_md"
fi
if [ -n "$opt_note" ]; then
echo ""
echo "$opt_note"
fi
} > "$RUNNER_TEMP/pr-body.md"
# The branch is pushed at this point, so a failure here must not
# vanish: the thread would otherwise keep the findings comment with
Expand Down
6 changes: 5 additions & 1 deletion apps/test-suite/tests/FileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,16 @@ export async function test({ describe, expect, it, ...t }: JasmineInterface) {
});
});

it('Allows reading files from assets', () => {
it('Allows reading files from assets', async () => {
const dir = new Directory(Paths.bundle);

if (Platform.OS === 'ios') {
expect(dir.list().map((i) => i.name)).toContain('Info.plist');
expect(new File(Paths.bundle, 'Info.plist').size > 2000).toBe(true);

const privacyManifest = new File(Paths.bundle, 'PrivacyInfo.xcprivacy');
expect(await privacyManifest.text()).toContain('<key>NSPrivacyAccessedAPITypes</key>');
expect((await privacyManifest.bytes()).length).toBeGreaterThan(100);
} else {
expect(dir.list().map((i) => i.name)).toContain('expo-root.pem');
expect(new File(Paths.bundle, 'expo-root.pem').size > 1000).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion apps/test-suite/tests/SQLite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ CREATE TABLE IF NOT EXISTS users (user_id INTEGER PRIMARY KEY NOT NULL, name VAR
it(`should use newer SQLite version`, async () => {
const db = await SQLite.openDatabaseAsync(':memory:');
const row = await db.getFirstAsync<{ 'sqlite_version()': string }>('SELECT sqlite_version()');
expect(semver.gte(requireNotNull(row)['sqlite_version()'], '3.49.1')).toBe(true);
expect(semver.gte(requireNotNull(row)['sqlite_version()'], '3.53.3')).toBe(true);
await db.closeAsync();
});

Expand Down
13 changes: 13 additions & 0 deletions docs/.oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,18 @@
"next-env.d.ts",
"ui/components/EASCLIReference/data/eas-cli-commands.json",
"components/plugins/permissions/data/android.json"
],
"overrides": [
{
"files": [
"pages/versions/unversioned/sdk/ui/swift-ui/*.mdx",
"pages/versions/unversioned/sdk/ui/jetpack-compose/*.mdx",
"pages/versions/v57.0.0/sdk/ui/swift-ui/*.mdx",
"pages/versions/v57.0.0/sdk/ui/jetpack-compose/*.mdx"
],
"options": {
"printWidth": 68
}
}
]
}
2 changes: 1 addition & 1 deletion docs/pages/additional-resources/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
modificationDate: July 28, 2026
modificationDate: August 18, 2026
title: Additional resources
description: A reference of resources that are useful to learn about Expo tooling and services.
---
Expand Down
21 changes: 19 additions & 2 deletions docs/pages/eas/observe/configuration.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
---
title: Configure EAS Observe
sidebar_title: Configuration
description: Control how EAS Observe collects and dispatches metrics, including environment settings, development mode, and custom endpoints.
description: Control how EAS Observe collects and dispatches metrics, including environment settings, development mode, custom endpoints, and data ingestion.
---

Configure EAS Observe at runtime to fit your app's build setup, environment, and data routing. This page covers enabling metrics in development, sampling, using a custom endpoint, and separating data by environment. For the runtime methods themselves, see the [`configure()`](/versions/latest/sdk/observe/#configureconfig) and [`dispatchEvents()`](/versions/latest/sdk/observe/#dispatchevents) API references.
import { ContentSpotlight } from '~/ui/components/ContentSpotlight';

Configure EAS Observe at runtime to fit your app's build setup, environment, and data routing. This page covers enabling metrics in development, sampling, using a custom endpoint, separating data by environment, and turning off data ingestion from the dashboard. For the runtime methods themselves, see the [`configure()`](/versions/latest/sdk/observe/#configureconfig) and [`dispatchEvents()`](/versions/latest/sdk/observe/#dispatchevents) API references.

## Sampling

Expand Down Expand Up @@ -74,3 +76,18 @@ The path includes your project ID before the standard `/v1/metrics` and `/v1/log
All metrics are grouped by environment. The environment value is derived from `process.env.NODE_ENV` by default (falling back to `'production'` if unset). To override it, use [`configure({ environment })`](/versions/latest/sdk/observe/#configureconfig).

The environment is a metadata tag attached to each metric and is independent of how the bundle was built. To control whether debug-build metrics are dispatched, see [Enable metrics in development](#enable-metrics-in-development). To disable all dispatching globally, use `configure({ dispatchingEnabled: false })`.

## Turn off data ingestion

The options above control what your app sends. You can also stop EAS Observe from accepting data on the server side, without shipping an app update, using the **Observe data ingestion** toggle in the EAS dashboard:

- **Account level**: open [Account settings](https://expo.dev/accounts/[account]/settings) and turn off **Observe data ingestion**. This pauses ingestion for all projects in the account.
- **Project level**: open [Project settings](https://expo.dev/accounts/[account]/projects/[project]/settings) and turn off **Observe data ingestion**. This pauses ingestion for that project only.

<ContentSpotlight
alt="The Observe data ingestion toggle in the EAS dashboard account settings."
src="/static/images/expo-observe/observe-data-ingestion-light.webp"
darkSrc="/static/images/expo-observe/observe-data-ingestion-dark.webp"
/>

While ingestion is paused, EAS Observe does not accept events, metrics, or logs sent by your apps. Data that was already ingested stays available in the dashboard and the CLI. Turn the toggle back on to resume ingestion.
2 changes: 1 addition & 1 deletion docs/pages/versions/unversioned/sdk/age-range.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function App() {
});
setResult(ageRange);
} catch (error) {
setResult({ error: error.message });
setResult({ error: error instanceof Error ? error.message : String(error) });
}
};

Expand Down
17 changes: 17 additions & 0 deletions docs/pages/versions/unversioned/sdk/font.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ On Android and iOS, the plugin allows you to embed font files at build time whic
"weight": 800
}
]
},
{
"fontFamily": "Roboto Flex",
/* @info A variable font file. It backs every definition below, so the path is shared. */
"path": "./path/to/RobotoFlex.ttf",
/* @end */
"fontDefinitions": [
{ "weight": 400 },
{ "weight": 700 },
/* @info The `slnt` axis draws the italic out of the same file. */
{ "weight": 400, "style": "italic", "axes": { "slnt": -10 } }
/* @end */
]
}
]
},
Expand Down Expand Up @@ -148,6 +161,10 @@ const styles = StyleSheet.create({

</SnackInline>

### Variable fonts

The `Roboto Flex` entry in the example above is a variable font: one file that holds many faces. You select a face with the `fontWeight` and `fontStyle` style props. Variable fonts also work with [`useFonts`](#usefonts). To learn more, see [Variable fonts](/develop/user-interface/fonts/#variable-fonts) in the Fonts guide.

## API

```js
Expand Down
14 changes: 9 additions & 5 deletions docs/pages/versions/unversioned/sdk/notifications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,10 @@ export default function App() {

// Handle URL from expo push notifications
const response = Notifications.getLastNotificationResponse();

return response?.notification.request.content.data.url;
const notificationUrl = response?.notification.request.content.data?.url;
if (typeof notificationUrl === 'string') {
return notificationUrl;
}
},
subscribe(listener) {
const onReceiveURL = ({ url }: { url: string }) => listener(url);
Expand All @@ -323,13 +325,15 @@ export default function App() {

// Listen to expo push notifications
const subscription = Notifications.addNotificationResponseReceivedListener(response => {
const url = response.notification.request.content.data.url;
const url = response.notification.request.content.data?.url;

// Any custom logic to see whether the URL needs to be handled
//...

// Let React Navigation handle the URL
listener(url);
if (typeof url === 'string') {
// Let React Navigation handle the URL
listener(url);
}

// Clear the stored response so the next getInitialURL call does not redirect to the same URL again
Notifications.clearLastNotificationResponse();
Expand Down
Loading
Loading